Generate React Native deep linking config
Enter your domain and the screens that should open from a link. We write the React Navigation linking config, the iOS entitlement and AppDelegate hooks, the Android intent filters, or the Expo app.json.
Your config
Each file shows where it goes in your project.
linking.ts
src/navigation/linking.ts (Pass it to NavigationContainer as the linking prop.)
import type { LinkingOptions } from '@react-navigation/native';
export const linking: LinkingOptions<ReactNavigation.RootParamList> = {
prefixes: [
'https://links.example.com',
'myapp://',
],
config: {
screens: {
ProductDetails: 'product/:id',
Cart: 'cart',
},
},
};
// <NavigationContainer linking={linking} fallback={<Text>Loading...</Text>}>YourApp.entitlements
ios/YourApp/YourApp.entitlements (Or add the Associated Domains capability in Xcode under Signing & Capabilities.)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:links.example.com</string>
</array>
</dict>
</plist>AppDelegate-linking.swift
ios/YourApp/AppDelegate.swift (Hands links to React Native's Linking module.)
// Add both methods inside your AppDelegate class.
// The React Native 0.77+ template already has `import React`.
func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
return RCTLinkingManager.application(app, open: url, options: options)
}
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
return RCTLinkingManager.application(
application,
continue: userActivity,
restorationHandler: restorationHandler
)
}Info-url-types.xml
ios/YourApp/Info.plist (Add inside the top-level dict.)
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>AndroidManifest-intent-filters.xml
android/app/src/main/AndroidManifest.xml (Add inside <activity android:name=".MainActivity">, which keeps android:launchMode="singleTask".)
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="links.example.com" />
<data android:pathPrefix="/product/" />
<data android:path="/cart" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" />
</intent-filter>Now generate the files your domain serves
The app config opens your app only after links.example.com serves an AASA file and an assetlinks.json file that name it. Both generators open with this app filled in.
Already serving the files?
The deep link checker fetches both files from links.example.com and reports what a device would read.
How it works
- 1. Pick your app type
- Bare React Native edits the iOS and Android projects. Expo sets the same values in app.json.
- 2. Enter your link domains
- Each host becomes a React Navigation prefix, an Associated Domains entry and an Android host.
- 3. Map paths to screens
- Write /product/:id -> ProductDetails for each screen. Use Shop.Cart to nest a screen in a navigator.
- 4. Copy each file to its path
- The label above every file says where it goes. Add the domain files next, then test on a device.
Reference
The fields
Only the domain and the routes are required. The identifiers fill in app.json and prefill the domain-file generators.
| Field | What to enter and where to find it |
|---|---|
| App type | Bare React Native, or Expo. Expo writes app.json fields and needs a new native build after a change. |
| React Navigation 7 API | Dynamic passes a linking object to NavigationContainer. Static puts a linking entry on each screen of createXNavigator. |
| Link domains | The hosts that serve your links, one per line, such as links.example.com. No https:// and no path. |
| Routes | One /path -> ScreenName per line. :id is a parameter, :id? is optional, and /* matches anything left. Use a dot to nest a screen, and put its path under the parent's path. |
| Custom URL scheme | Optional. The part before ://, in lowercase. It is not verified, so it suits testing and places that reject https. |
| iOS bundle ID | Optional. In Xcode under Signing & Capabilities, or PRODUCT_BUNDLE_IDENTIFIER in the project. |
| App ID prefix | Optional. Ten characters, usually your Team ID under Membership details in your Apple Developer account. |
| Android package name | Optional. The applicationId in android/app/build.gradle. |
Versions these snippets target
| Tool | Version |
|---|---|
| React Native | 0.77 and later, with the Swift AppDelegate. Earlier Objective-C projects call the same two RCTLinkingManager methods. |
| React Navigation | 7.x, both the dynamic linking prop and the static API. |
| Expo | SDK 50 and later. Expo Router derives its routes from the files under app/, so the generator lists the files. |
| iOS | Universal Links through the Associated Domains capability. |
| Android | App Links with android:autoVerify, which Android 6.0 and later check against assetlinks.json. |
When a link does not open the right screen
| What you see | Likely cause and fix |
|---|---|
| iOS opens Safari, not the app | The Associated Domains entry is missing, the host differs from the one in your AASA file, or the App ID in that file is wrong. Check the domain with the deep link checker, then reinstall the app because iOS reads the file at install. |
| Android shows a chooser | Verification failed. assetlinks.json is missing, is served through a redirect, or lists a different package or SHA-256 certificate fingerprint. |
| The app opens on the wrong screen | React Navigation ranks patterns by how specific they are, so the order in the config does not decide. Look for a broad pattern such as /* that also fits the link, and check that each screen name matches your navigator exactly. |
| Cold start works, a warm link does nothing | On iOS, the continue userActivity method is missing from AppDelegate. On Android, MainActivity needs android:launchMode="singleTask". |
| The custom scheme works, https does not | The scheme needs no domain file. https links need the AASA file, assetlinks.json and the native settings together. |
| Expo changes have no effect | associatedDomains and intentFilters are native settings. Make a development build or run expo prebuild again. Expo Go cannot verify your domain. |
Need deferred deep links and install attribution?
The config on this page opens a screen when the app is installed and a person taps a link. It cannot help the person who taps a link before installing: they go to the store, then open the app with no link in hand.
Deferred deep links, which carry that link through the install, and install attribution need an SDK. The WarpLink React Native SDK delivers the original link on first open and records which link led to the install, next to the config you built here.
What a React Native app needs for links
A link opens a screen through four pieces. React Navigation maps URL paths to screens in its linking config. iOS needs the Associated Domains entitlement for your host and two AppDelegate methods that hand the URL to React Native. Android needs an intent filter with android:autoVerify so the system opens your app without asking. Your domain serves an apple-app-site-association file and an assetlinks.json file that name the app.
The linking config handles a cold start and a link received while the app runs. This page writes the first three pieces. The AASA and assetlinks generators write the files your domain serves, and the deep link checker reads them back from your live domain. Test on a signed build after each change, because simulators and emulators do not verify domains the way devices do.
Frequently asked questions
How do I map nested screens?
Write the parent and the screen with a dot, such as Shop.Product, and the generator nests the screens object the way your navigators nest. The nested path is written relative to its parent, so /shop/:id under Shop becomes :id. The static API lists root screens only, so nest by declaring the linking entry inside each navigator.
Do I need a custom scheme?
Not for links on your own domain. Universal Links and App Links use https URLs. A custom scheme is useful for local testing and for places that do not accept https, and it needs no domain file.
How do I support two domains?
Put one host on each line. Both go into the React Navigation prefixes, the Associated Domains entitlement and the Android intent filter. Each host serves its own AASA and assetlinks.json file.
Why does the app open the wrong screen?
React Navigation ranks patterns by how specific they are, so listing a specific path first changes nothing. Look for a broad pattern such as /* that also fits the link, a nested screen that repeats its parent's path, and a screen name that differs from the one in your navigator.
Should I use the dynamic or the static API?
Use whichever matches how you build navigators. The dynamic API is a linking object with a config.screens tree. The static API, from React Navigation 7, declares the path on each screen and passes only the prefixes to the Navigation component.
Does this work with Expo Router?
Expo Router builds its linking from the files under app/, so it needs no linking config. Choose Expo and the generator writes app.json and lists the file for each path, such as app/product/[id].tsx.
What must I test on a device?
Open a link from a cold start and from the background on both platforms, and tap a link from Notes or Messages. Simulators and emulators do not replace a signed build for verification.
Related tools
- AASA generatorBuild the apple-app-site-association file for Universal Links from your Team ID, bundle ID and path rules.
- assetlinks.json generatorBuild the Digital Asset Links file for Android App Links from your package name and signing certificate fingerprints.
- Deep link checkerOne report for iOS and Android. We test the AASA file, assetlinks.json, redirects, headers and the paths you list.
We handle the link layer for you
Add the WarpLink React Native SDK for deep links, deferred deep links and install attribution on top of this config.