Deep Linking
Deep linking opens a specific screen in your app from a URL β whether from a custom scheme (myapp://user/42) or a universal link (https://myapp.com/user/42). React Native Navigation maps URLs to screens and presents the matched content as a modal on top of whatever the user is currently doing, so their navigation state is preserved when they dismiss the modal.
Setting up linking#
Call Navigation.setLinking() once during app startup, alongside your Navigation.registerComponent calls:
When a deep link arrives:
- RNN parses the URL, strips the longest matching prefix, and matches the path against the
screenstree. - The matched chain is wrapped in a
stackand presented viashowModal. The user can dismiss the modal to return to their previous flow. - If no route matches, the optional
fallbackcallback is invoked.
Path parameters (:id) and query parameters are extracted and passed as passProps to each component in the modal.
Configuration#
prefixes#
Array of URL prefixes your app handles. Custom schemes and universal-link hosts are both supported. The longest matching prefix wins.
config.screens#
Map of screen names (must match the names you registered with Navigation.registerComponent) to URL path patterns.
String value β a leaf route:
Object value β a route with nested children:
Object value with no path β a grouping node that contributes a screen to the chain but consumes no URL segments:
Path parameters#
Use :paramName in a pattern segment to capture it. Path params are merged with query params and handed to that segment's component as passProps. Path params win on key collision.
Query parameters#
Query string values are parsed automatically and merged into every segment's passProps:
Reserved keys#
The query/path keys ref and key are not forwarded as passProps. React reserves these names (ref is consumed by React itself; passing a string ref to a component crashes under React 19's stricter validation). RNN silently drops them and logs a dev-mode warning. Rename any conflicting URL parameters before they reach your links:
Customizing presentation#
By default, every matched link becomes a modal containing a stack with the matched chain pushed in order. If you need to customize either the layout or the navigation command itself, the config exposes two hooks.
getModal β customize the modal layout#
Provide a function that returns the Layout to present. Useful for adding default options, wrapping in a different layout, or skipping certain matches conditionally.
Return undefined from getModal to skip presenting a modal for a particular match (e.g. ignore certain paths).
onLink β full escape hatch#
For complete control β pushing onto an existing stack instead of showing a modal, dismissing existing modals first, or composing multiple commands β provide onLink. When supplied, RNN does not present anything itself; you call navigation commands yourself.
When onLink is set, getModal is ignored.
Match payload#
Both getModal and onLink receive the same RouteMatch object:
Fallback for unmatched URLs#
If a received URL has no matching prefix or doesn't match any configured route, the optional fallback callback is invoked. Use it for logging, analytics, or routing to a "not found" screen.
Deferred deep links#
Two readiness gates control when links are processed; both must be open before a link is dispatched.
Automatic root gate#
RNN automatically defers link processing until your first Navigation.setRoot() resolves. This handles the common case where the OS delivers an initial URL before your app has a root window β the link is queued and replayed once setRoot mounts the root.
You don't need to opt into this behavior; it always applies.
Optional isReady gate#
For application-level gates such as authentication, supply an isReady predicate. The predicate is consulted on every incoming link; when it returns false, the link is queued. Once the gate opens, call setLinkingReady(true) to flush queued links in order.
setLinkingReady(true) overrides isReady once called, so you can also drive the gate purely with explicit calls if you prefer.
Manual handling#
Some links don't arrive via the system URL handler β push-notification payloads, branch.io tokens, App Clips. Feed them into the same pipeline with Navigation.handleDeepLink(url):
This runs the URL through parse β match β present exactly like a system-delivered link, including readiness gates.
If you'd rather hand the URL off natively (e.g. from a UNUserNotificationCenterDelegate you already maintain), call [self dispatchDeepLinkURL:url] inside your RNNAppDelegate subclass β same behavior, with cold-start queueing built in. See Native setup β iOS β Notification taps for an example.
Complete example#
With this configuration:
myapp://homeopens the Home screen as a modal on top of the current screen.myapp://user/42opens Profile as a modal with{ id: '42' }as props.myapp://settings/notificationsopens a modal containing a stack withSettingsβNotificationspushed in order.- Links arriving before
setRootresolves are queued automatically and replayed. - Links arriving before login are queued and replayed when
onLoginComplete()runs.
Native setup#
The framework consumes URLs from React Native's Linking API. You still configure the URL schemes and universal links at the platform level β but, unlike most React Native apps, you don't need to hand-write application:openURL: or application:continueUserActivity: glue. As long as your AppDelegate subclasses RNNAppDelegate, custom-scheme openings and universal links are forwarded to JS automatically, including cold-start URLs that arrive before the React runtime is ready.
iOS#
1. Register your URL scheme in Info.plist:
2. (Optional) Universal links. Add the Associated Domains entitlement and host an apple-app-site-association file β see Apple's docs. RNNAppDelegate already implements application:continueUserActivity:restorationHandler: and forwards browser-activity URLs through the same pipeline.
3. (Optional) Notification taps. Notifications aren't routed automatically because most apps already own UNUserNotificationCenter.current.delegate via Firebase / OneSignal / etc. From your existing notification handler, hand the URL off to RNN:
dispatchDeepLinkURL: is inherited from RNNAppDelegate. It safely handles cold-start (URLs arriving before the React runtime is ready are queued and flushed automatically once content first renders) so you can call it from anywhere β push handlers, deferred-deep-link SDKs, branch.io callbacks, etc.
The JS equivalent is Navigation.handleDeepLink(url); use whichever fits the layer you're working at.
Android#
Add an intent filter to your MainActivity in AndroidManifest.xml:
For App Links, add android:autoVerify="true" and host an assetlinks.json. See Android's documentation.
NavigationActivity already forwards both cold-start (getIntent()) and warm (onNewIntent) URLs to React Native's Linking module β no extra Java/Kotlin needed. Notification taps that carry a deep-link URI (via PendingIntent with ACTION_VIEW) flow through the same onNewIntent path automatically.
API reference#
Navigation.setLinking(config)#
Configure deep link handling. Subsequent calls reconfigure cleanly (the previous subscription is torn down).
| Field | Type | Required | Description |
|---|---|---|---|
prefixes | string[] | Yes | URL prefixes to match. Longest wins. |
config.screens | ScreensConfig | Yes | Screen-to-path mapping. |
getModal | (match) => Layout \| undefined | No | Build a custom modal layout for the match. |
onLink | (match) => void | No | Full escape hatch. Overrides default and getModal. |
fallback | (url) => void | No | Called when a URL doesn't match any prefix or route. |
isReady | () => boolean | No | Predicate gating link processing. |
Navigation.handleDeepLink(url)#
Feed a URL into the pipeline as if it had been delivered by the OS.
Navigation.setLinkingReady(ready)#
Set the user-controlled readiness gate. Passing true flushes queued links; passing false blocks future links until called again with true.