Navigation
The AI can suggest screens for the user to visit inside your app. The widget renders a button for each suggestion; tapping it triggers your navigation logic.
For navigation to work, the AI needs two pieces of information:
- Which screens exist — uploaded from your codebase via the CLI as a navigation schema.
- Where the user is right now — passed at runtime via Context (the
currentScreenkey is conventional but not required).
Handling Suggestions
Two callbacks are involved:
<Qafka
// projectId, endUserId, …
onNavigationSuggest={(suggestion) => {
// Always fires when the AI suggests a destination, even if the user ignores it
analytics.track('nav_suggested', { screen: suggestion.screenName })
}}
onNavigationAction={(suggestion) => {
// Fires when the user taps the navigation button.
// Omit this prop to let the SDK navigate automatically via Expo Router.
navigation.navigate(suggestion.route)
}}
/>A NavigationSuggestion has the following shape:
{
screenName: string // Display name (e.g. "Cart")
route: string // Route key for your navigator
deeplink?: string | null // Optional deeplink URL
params?: Record<string, any> // Route params, if the schema declares any for this screen
confirmed?: boolean // True if AI already confirmed user intent
message?: string // Optional natural-language label
reasoning?: string // Optional natural-language explanation of why this screen was suggested
trigger?: 'user' | 'ai' // 'user' — explicit request; 'ai' — proactive suggestion
source?: 'text' | 'voice' // Which chat mode produced the suggestion
}If you don’t pass onNavigationAction, the SDK auto-navigates via expo-router’s router.push(suggestion.route ?? suggestion.screenName) (the route is normalized to start with /). When expo-router isn’t installed, the call is a silent no-op — wire onNavigationAction to your own navigator in that case.
Customizing the Button
Three levels of customization, from light to heavy:
1. Just change the label text:
<Qafka
// projectId, endUserId, …
navigationLabelFormat={(screen) => `Go to ${screen}`}
/>2. Replace the entire button:
import { TouchableOpacity, Text } from 'react-native'
<Qafka
// projectId, endUserId, …
NavigationButtonComponent={({ screenName, suggestion, onPress, theme, label, icon }) => (
<TouchableOpacity onPress={onPress} style={myStyles(theme)}>
{icon ? <Icon name={icon} /> : null}
<Text>{label}</Text>
</TouchableOpacity>
)}
/>The component receives { screenName, suggestion, onPress, theme, style, label, icon } — label is the already-formatted text (after navigationLabelFormat), suggestion is the full NavigationSuggestion object.
3. Skip the button entirely by listening to onNavigationSuggest and rendering your own UI elsewhere in the app — chips, banner, push notification, anything.
Filtering by Auth State
Each screen in your navigation schema can be tagged with one of four accessType values from the dashboard’s navigation rules editor:
accessType | Meaning |
|---|---|
public | Anyone — default |
authenticated | Only suggested when the user is signed in |
unauthenticated | Only suggested when the user is signed out (e.g. Login, Sign Up) |
restricted | Never suggested — useful for admin-only or deprecated screens |
Pass isAuthenticated to the widget so the AI knows which side of the gate the user is on:
<Qafka
// projectId, endUserId, …
isAuthenticated={!!user}
/>true—authenticatedandpublicscreens eligible;unauthenticatedhiddenfalse—unauthenticatedandpublicscreens eligible;authenticatedhidden- omitted — the SDK sends its version with every request, which puts the backend in strict mode: only
publicscreens (and screens with no navigation rules configured at all) are eligible;authenticatedandunauthenticatedscreens are both hidden. PassisAuthenticatedexplicitly if you want auth-gated screens to be suggested.
The widget forwards this flag automatically inside the context payload — no extra wiring needed.
Navigation Schema
The AI only suggests screens it knows about. Generate the schema from your codebase and upload it:
npx qafka analyze
npx qafka uploadanalyze scans your project and writes navigation-schema.json. upload sends it to your project’s backend after you’ve authenticated with qafka login.
Choosing the right analyze mode
qafka analyze supports React Native (Expo Router or React Navigation) and Next.js projects, or a website with --web <url>. By default it runs a deterministic, local parser and makes no network calls.
- Deterministic (default) — fast, free, no login required. The right starting point for most projects.
--ai— skip the deterministic parser and send source files to an LLM. Useful when the deterministic parser misses screens (custom navigation patterns, dynamic registration, non-standard libraries).--auto— fall back to--aiautomatically if the deterministic parser finds nothing. This fallback only applies to React Native; Next.js analysis always stays local and reports a diagnostic instead of falling back.--deep— after the normal analyze, ask the AI to extract per-screen metadata (descriptions, prop types, usage hints) from local per-screen summaries — never raw source. Requiresqafka login. The extra metadata becomes part of every navigation prompt. Combine with--incrementalto only re-extract screens that changed since the last--deeprun, and--print-summariesto see exactly what would be sent before running it for real.--web <url>— crawl a website instead of analyzing app source, and upload the resulting web navigation rules directly.
For most React Native projects the recommended workflow is: start with qafka analyze, add --deep once you’re happy with the schema, and rerun with --deep --incremental after big screen-level changes. See CLI for the full flag reference and website crawling options.
Editing in the Dashboard
After upload, you can edit per-screen rules (auth requirement, descriptions, deeplinks, navigation hints) from the dashboard’s navigation editor — including overriding any accessType the AI inferred during --deep analysis.
Navigation in Voice
Navigation suggestions also fire during voice chat, using the same NavigationSuggestion shape (source: 'voice'). Both cases only ever call onNavigationSuggest — onNavigationAction isn’t used in voice:
- Explicit request (
trigger: 'user') — the SDK callsonNavigationSuggestimmediately and closes the voice session once the AI finishes speaking. - Proactive suggestion (
trigger: 'ai') — the voice page shows a suggestion button; tapping it callsonNavigationSuggestand closes the voice session right away.