Skip to content
Dashboard

Installation

Detour provides a React Native SDK (@swmansion/react-native-detour) compatible with Expo and bare React Native projects.

A Detour app in the Dashboard is required before the SDK can resolve links. See Getting Started.

Terminal window
npm install @swmansion/react-native-detour

For Expo projects (including prebuild), install peer dependencies with Expo CLI so versions are matched to your Expo SDK:

Terminal window
npx expo install expo-application expo-clipboard expo-constants expo-localization @react-native-async-storage/async-storage

If you are not using Expo CLI, install peer dependencies with your package manager and pin versions compatible with your React Native/Expo Modules setup.

Terminal window
npm install expo-application expo-clipboard expo-constants expo-localization @react-native-async-storage/async-storage

Four of the peer dependencies above are Expo Modules:

  • expo-application
  • expo-clipboard
  • expo-constants
  • expo-localization

They ship native code that has to be autolinked into your android/ and ios/ projects. An Expo project does this for you. A bare React Native CLI project needs Expo Modules added once:

Terminal window
npx install-expo-modules@latest

This sets up Expo Modules autolinking (and on iOS raises the deployment target to the Expo Modules minimum).

install-expo-modules also switches the native JS bundling step to Expo CLI (export:embed). That requires metro.config.js to extend expo/metro-config, which is a superset of @react-native/metro-config and safe in bare projects. The tool usually migrates the file for you, but verify it, especially if you had customized it:

metro.config.js
const { getDefaultConfig } = require("expo/metro-config");
const { mergeConfig } = require("@react-native/metro-config");
module.exports = mergeConfig(getDefaultConfig(__dirname), {});

Then align the Expo package versions to your setup:

Terminal window
npx expo install --fix

Deferred-link matching builds a probabilistic fingerprint of the device. The SDK reads three values for it:

  • device model
  • manufacturer
  • OS version

Those values come from a device info library. Install one of the two supported ones. Both are optional peer dependencies, and the SDK uses whichever it finds:

Terminal window
npx expo install expo-device

Installing the package does not register incoming links with iOS or Android.

Copy the generated ios.associatedDomains and android.intentFilters values from App configuration in the Dashboard into app.json, app.config.js, or app.config.ts:

app.json
{
"expo": {
"ios": {
"associatedDomains": ["applinks:<your-host>"]
},
"android": {
"intentFilters": [
{
"action": "VIEW",
"autoVerify": true,
"data": [
{
"scheme": "https",
"host": "<your-host>",
"pathPrefix": "/<app-hash>"
}
],
"category": ["BROWSABLE", "DEFAULT"]
}
]
}
}
}

Use the exact Dashboard output: it also contains any required legacy short-link prefixes. New short links reuse /<app-hash> and do not require another native build.

If your app is served from a custom domain, the Dashboard generates both values for that domain. Assign the domain before you copy them. Changing the host an app is assigned to later needs a new build. Do not add hosts it did not generate: on Android 11 and lower that breaks App Link verification for every host at once, see Verification.

The same host belongs in the handler’s hosts list, which is separate from the native configuration.

Custom schemes are optional and are not generated by the Dashboard. If you use links such as myapp://product/42, add the scheme separately:

app.json
{
"expo": {
"scheme": "myapp"
}
}

These are build-time settings. After changing the app config, create a new native build. In a Continuous Native Generation workflow, regenerate the native projects with npx expo prebuild --clean, then run npx expo run:ios or npx expo run:android. Alternatively, create a new EAS development build. Reloading Metro or publishing an OTA update does not apply native configuration changes.

In Xcode, open your app target, select Signing & Capabilities, add the Associated Domains capability, and enter applinks:<your-host>. The resulting entitlements entry is:

<App>.entitlements
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:<your-host></string>
</array>

Paste the generated Dashboard filter into the MainActivity declaration in AndroidManifest.xml:

AndroidManifest.xml
<activity
android:name=".MainActivity"
android:exported="true">
<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="<your-host>" />
<data android:pathPrefix="/<app-hash>" />
<!-- The Dashboard adds any required legacy short-link prefixes. -->
</intent-filter>
</activity>

Use the exact host and paths generated for your app.

The Dashboard does not generate custom-scheme registration. If you use scheme links, add CFBundleURLTypes to the iOS Info.plist:

Info.plist
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>

Add a separate filter to the Android MainActivity:

AndroidManifest.xml
<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>

DetourProvider reads cold-start and runtime URLs through React Native’s Linking API. Verify that your AppDelegate forwards Universal Link and custom-scheme callbacks to RCTLinkingManager.

The snippets below are for an AppDelegate that does not already provide this forwarding. If its superclass or another integration handles links, preserve that behavior and merge the handlers instead of adding duplicate forwarding or replacing the existing implementation.

import React
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
return RCTLinkingManager.application(
application,
continue: userActivity,
restorationHandler: restorationHandler
)
}
func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
return RCTLinkingManager.application(app, open: url, options: options)
}

With the standard MainActivity : ReactActivity, no Detour-specific callback is required. React Native reads the cold-start VIEW intent from the activity and forwards later onNewIntent calls to Linking.

Set android:launchMode="singleTask" when links should be delivered to the existing MainActivity instance. React Native then emits the runtime url event consumed by DetourProvider. If you use a custom activity that does not inherit React Native’s default intent handling, you must forward its intents to React Native yourself.

Both values come from API configuration in the Dashboard: the appID and the publishable apiKey. See Getting Started.

import { type Config } from '@swmansion/react-native-detour';
export const detourConfig: Config = {
apiKey: process.env.EXPO_PUBLIC_DETOUR_API_KEY!,
appID: process.env.EXPO_PUBLIC_DETOUR_APP_ID!,
shouldUseClipboard: true,
};

Keep apiKey and appID in environment variables or another secure configuration layer.

  1. Implement the provider and link handling in Usage.
  2. Pick a runnable app pattern in Examples.
  3. Verify the native link configuration in Testing.
  4. Fill in the store declarations from Privacy before you submit a build.
  5. If upgrading existing code, follow Migration to v2 and start from From 0.x or From 1.0.1.