# Usage

Detour delivers links to your app through a single provider. `DetourProvider` resolves the incoming link, whether deferred, Universal/App, or custom scheme, and `useDetourContext()` returns the result. You then navigate with your router, or navigate manually.

## Provider setup

```ts
import { DetourProvider, type Config } from '@swmansion/react-native-detour';

const config: Config = {
  apiKey: process.env.EXPO_PUBLIC_DETOUR_API_KEY!,
  appID: process.env.EXPO_PUBLIC_DETOUR_APP_ID!,
  shouldUseClipboard: true,
  // linkProcessingMode: 'all' // default
};

export default function RootLayout() {
  return (
    <DetourProvider config={config}>
      <RootNavigator />
    </DetourProvider>
  );
}
```

:::caution[One provider per app]
Mount exactly one `DetourProvider` in your app tree. A second one disables analytics until only one remains.
:::

:::note[Expo Web]
`DetourProvider` is disabled on web. It returns a no-op context (`isLinkProcessed: true`, `link: null`) and processes no links.
:::

For complete, runnable integrations of each navigation style, see [Examples](https://detour.swmansion.com/docs/sdk/react-native/examples).

## Handle resolved links

```ts
import { useDetourContext } from '@swmansion/react-native-detour';
import { useEffect } from 'react';
import { useRouter } from 'expo-router';

export function RootNavigator() {
  const { isLinkProcessed, link, clearLink } = useDetourContext();
  const router = useRouter();

  useEffect(() => {
    if (!isLinkProcessed || !link) return;

    router.replace({
      pathname: link.pathname,
      params: link.params,
    });

    clearLink();
  }, [clearLink, isLinkProcessed, link, router]);

  if (!isLinkProcessed) {
    return null;
  }

  return <AppStack />;
}
```

### `link` structure

`useDetourContext()` returns a `link` object, or `null` when no link was detected.

<PropertyList>
  <Property name="link.route" type={"string"}>
    Full in-app route, query string included: `/details?utm=1`.
  </Property>
  <Property name="link.pathname" type={"string"}>
    Path without the query: `/details`.
  </Property>
  <Property name="link.params" type={"Record<string, string>"}>
    Query params, already parsed.
  </Property>
  <Property name="link.url" type={"string | URL"}>
    The original URL, or the raw value when it is not a URL.
  </Property>
  <Property name="link.type" type={"LinkType"} typeHref="/sdk/react-native/api-reference#linktype">
    Where the link came from: `'deferred'`, `'verified'`, or `'scheme'`.
  </Property>
</PropertyList>

Use `clearLink()` after handling navigation to avoid repeated redirects.

## Link processing modes

`Config.linkProcessingMode` controls which link sources the SDK handles:

| Value             | Deferred | Universal / App | Custom scheme |
| ----------------- | -------- | --------------- | ------------- |
| `'all'` (default) | Yes      | Yes             | Yes           |
| `'web-only'`      | Yes      | Yes             | No            |
| `'deferred-only'` | Yes      | No              | No            |

Use `'deferred-only'` when Expo Router `+native-intent.tsx` already handles runtime and initial URLs.

## Click limits

Before it delivers a Universal Link or App Link, the SDK checks the click against your plan's limit. Within the limit, links resolve normally.

### Over the limit

The link is **not** delivered:

- `DetourProvider` leaves `link` as `null`, so `useDetourContext()` returns no route to navigate to.
- `createDetourNativeIntentHandler` in resolve mode returns `fallbackPath`.

Either way the SDK logs `🔗[Detour:CLICK_LIMIT_ERROR]` with `clicksInPeriod` and `effectiveLimit`.

### Other link types

This flow applies only to Universal Links and App Links.

- **Deferred and short links** — the same plan limits apply on the backend, but the SDK raises no click-limit error. An over-limit link fails to resolve and returns `null`.
- **Custom scheme links** — resolved on device, never limit-checked.

:::note[Fail-open by design]
If the limit check cannot complete because of a network or backend error, the SDK delivers the link, so a temporary outage does not block navigation. Limits are enforced only on an explicit "denied" response.
:::

For per-plan limits, pay-as-you-go overage, and how to raise them, see [Billing & Payments](https://detour.swmansion.com/docs/platform/fundamentals/billing).

## Expo Router integration

:::tip[Recommended for Expo apps]
On Expo Router, use `createDetourNativeIntentHandler` in a [`+native-intent.tsx`](https://docs.expo.dev/router/advanced/native-intent/) file. It handles URLs at the router level, before any screen renders, so the user never sees the wrong screen.
:::

Expo Router's `+native-intent.tsx` intercepts and transforms incoming system URLs before the router processes them. `createDetourNativeIntentHandler` is a factory that builds such a handler, pre-configured for Detour link resolution.

### Basic setup

Create a `+native-intent.tsx` file in your app directory (e.g. `src/app/+native-intent.tsx`):

```ts title="src/app/+native-intent.tsx"
import { createDetourNativeIntentHandler } from "@swmansion/react-native-detour/expo-router";

export const redirectSystemPath = createDetourNativeIntentHandler();
```

:::note[This is intercept mode]
Any URL matching a Detour host is intercepted and the user opens the home screen, the default `fallbackPath` of `''`. `DetourProvider` still handles deferred links separately.
:::

### Resolve mode

To resolve Detour short links to their full destination URLs at the router level, pass a `config` object:

```ts title="src/app/+native-intent.tsx"
import { createDetourNativeIntentHandler } from "@swmansion/react-native-detour/expo-router";

export const redirectSystemPath = createDetourNativeIntentHandler({
  config: {
    apiKey: process.env.EXPO_PUBLIC_DETOUR_API_KEY!,
    appID: process.env.EXPO_PUBLIC_DETOUR_APP_ID!,
  },
});
```

In resolve mode, when a user taps a Detour short link (e.g. `https://abc123.godetour.link/xyz`), the handler resolves it to the full destination URL and routes directly to the correct screen, all before the first render.

See [Expo Router helper](https://detour.swmansion.com/docs/sdk/react-native/api-reference#expo-router-helper) for the full list of options (custom hosts, fallback path, timeout, route mapping, etc.).

### Custom domains

The handler only processes URLs whose hostname matches its `hosts` list, which
defaults to `[/\.godetour\.link$/i]`. An app served from a
[custom domain](https://detour.swmansion.com/docs/platform/advanced/custom-domain) needs its own entry. Without
one, a Universal Link on `links.yourdomain.com` is passed to Expo Router
unhandled and Detour never processes it.

```ts title="src/app/+native-intent.tsx"
import { createDetourNativeIntentHandler } from "@swmansion/react-native-detour/expo-router";

export const redirectSystemPath = createDetourNativeIntentHandler({
  hosts: ["links.yourdomain.com", /\.godetour\.link$/i],
  config: {
    apiKey: process.env.EXPO_PUBLIC_DETOUR_API_KEY!,
    appID: process.env.EXPO_PUBLIC_DETOUR_APP_ID!,
  },
});
```

Passing `hosts` **replaces** the default rather than adding to it. The example
lists both hosts, so the app keeps handling `.godetour.link` links as well. Keep
that second entry: if the custom domain is removed, or the paid plan lapses,
Detour serves the app's links from `.godetour.link` again, and a build that
listed the custom domain alone ignores them.

Each entry is matched against the hostname alone, in one of three forms:

- an exact hostname: `"links.yourdomain.com"`
- a suffix wildcard: `"*.yourdomain.com"`, when you serve links from several
  subdomains
- a regular expression: `/\.yourdomain\.com$/i`

`hosts` is consulted only after the OS has decided to open your app, so the same
host has to be in the native configuration as well: `associatedDomains` on iOS,
an `<intent-filter>` on Android. See
[Installation](https://detour.swmansion.com/docs/sdk/react-native/sdk-installation#native-link-configuration) for
where each one goes.

### Configure `DetourProvider` for native intent

When using `+native-intent.tsx` to handle Universal Links and App Links, configure the provider to only process deferred links:

```ts
const config: Config = {
  apiKey: process.env.EXPO_PUBLIC_DETOUR_API_KEY!,
  appID: process.env.EXPO_PUBLIC_DETOUR_APP_ID!,
  linkProcessingMode: "deferred-only",
};
```

This avoids double-processing: `+native-intent.tsx` handles runtime and initial URLs, while `DetourProvider` handles deferred deep links only.

### Advanced examples

The SDK repository covers two common native intent scenarios:

- **`expo-router-native-intent`** — resolve mode with a custom `mapToRoute` and scheme URL handling. Start here to transform resolved URLs before routing, for example to strip a path prefix, or to handle custom scheme links alongside Detour links.
- **`expo-router-advanced`** — Detour inside an auth- and onboarding-gated flow. Deferred and runtime links are preserved through sign-in and one-time onboarding before the target screen opens.

For all runnable examples, see [Examples](https://detour.swmansion.com/docs/sdk/react-native/examples).

### Alternative: catch-all route

If you prefer not to use `+native-intent.tsx`, keep `linkProcessingMode: 'all'` (the default) and add a catch-all route to your navigation stack.

`DetourProvider` then processes every link type: deferred, Universal/App, and custom scheme. Read the resolved link with `useDetourContext()` in a top-level component and navigate programmatically:

```ts
const { isLinkProcessed, link, clearLink } = useDetourContext();

useEffect(() => {
  if (!isLinkProcessed || !link) return;
  router.replace({ pathname: link.pathname, params: link.params });
  clearLink();
}, [isLinkProcessed, link]);
```

This was the primary integration pattern before the SDK added native intent support (pre-1.x). It still works. `+native-intent.tsx` resolves links before the first screen renders.

## React Navigation integration

If you use **React Navigation** instead of Expo Router, the SDK provides a linking adapter for React Navigation's [custom linking API](https://reactnavigation.org/docs/deep-linking?config=static#integrating-with-other-tools).

Detour resolves the incoming link, whether deferred, Universal/App, or scheme, and feeds the matched route to React Navigation. Navigation itself runs through your existing `linking` configuration.

The adapter needs a `DetourProvider` mounted **above** your `NavigationContainer`.

### Linking setup

```ts
import { DETOUR_LINKING_PREFIX, Detour } from "@swmansion/react-native-detour";

const linking = {
  prefixes: [DETOUR_LINKING_PREFIX],
  async getInitialURL() {
    return await Detour.getInitialURL();
  },
  subscribe(listener) {
    const subscription = Detour.addEventListener("url", ({ url }) => {
      listener(url);
    });

    return () => subscription.remove();
  },
  config: {
    // your existing React Navigation screen-to-path mapping
  },
};
```

Pass this object to your navigation container:

```tsx
<DetourProvider config={detourConfig}>
  <NavigationContainer linking={linking}>
    <RootNavigator />
  </NavigationContainer>
</DetourProvider>
```

The adapter does not pass the original URL to React Navigation. It rewrites every resolved link into an internal URL of its own, and that URL carries two additions:

- **The `detour://` prefix**, exported as `DETOUR_LINKING_PREFIX`. React Navigation only parses URLs whose prefix it recognizes, which is why the constant belongs in `prefixes`.
- **Two query params**, `fromDeepLink=true` and `linkType` (`deferred`, `verified`, or `scheme`). They arrive as route params, so a screen can distinguish a deep-link entry from a normal one.

A resolved route `/details?id=1` is therefore passed to React Navigation as:

```txt
detour://details?id=1&fromDeepLink=true&linkType=deferred
```

Your `config` screen mapping is unchanged. It still matches on `details`.

### Auth-gated deep links

For apps that gate screens behind sign-in or onboarding, let React Navigation hold the deep link until the target screen becomes reachable. Render the screens conditionally on auth state, and opt in to React Navigation's pending-link behavior on the navigator:

```tsx
// Stack.Navigator only accepts Stack.Screen as a direct child,
// so this is a render function rather than a component.
function renderAuthScreens({ isSignedIn, isOnboardingCompleted }) {
  if (!isSignedIn) {
    return <Stack.Screen name="SignIn" component={SignIn} />;
  }

  if (!isOnboardingCompleted) {
    return <Stack.Screen name="Onboarding" component={Onboarding} />;
  }

  return (
    <>
      <Stack.Screen name="Tabs" component={TabNavigator} />
      <Stack.Screen name="Details" component={Details} />
    </>
  );
}

<Stack.Navigator UNSTABLE_routeNamesChangeBehavior="lastUnhandled">
  {renderAuthScreens({ isSignedIn, isOnboardingCompleted })}
</Stack.Navigator>;
```

A deep link that arrives while the user is signed out is parsed, found unreachable because its target screen is not rendered yet, and remembered. Each time the rendered screen set changes, after sign-in and again after onboarding, React Navigation retries and opens the target screen.

For a complete setup, see the [`react-navigation-advanced`](https://detour.swmansion.com/docs/sdk/react-native/examples) example.

:::note[Cold start is buffered]
The adapter holds the initial URL until Detour finishes link processing, so `getInitialURL()` resolves only once the matched route is known.
:::

## Analytics

Mounting `DetourProvider` turns analytics on. With the provider in place, it logs one event by itself: `app_open`, a retention event sent once per session on first mount (cold start). Universal Link and App Link opens are not client events. The backend records them through the click endpoint (see [Click limits](#click-limits)).

Everything else you log yourself, through `DetourAnalytics`. The provider sends every event it receives.

```ts
import {
  DetourAnalytics,
  DetourEventNames,
} from "@swmansion/react-native-detour";

DetourAnalytics.logEvent(DetourEventNames.Purchase, {
  item: "subscription_premium",
  currency: "USD",
});

DetourAnalytics.logRetention("home_screen_opened");
```

### Opting out of automatic events

From **2.3.1** the `app_open` event can be turned off with
`shouldTrackAutomaticEvents`. Use it when the SDK must send no events until the
user has consented to tracking, for example under App Tracking Transparency on iOS, a
consent banner, or a regional privacy requirement:

```ts
const config: Config = {
  apiKey: process.env.EXPO_PUBLIC_DETOUR_API_KEY!,
  appID: process.env.EXPO_PUBLIC_DETOUR_APP_ID!,
  shouldTrackAutomaticEvents: false,
};
```

Only the automatic event is affected. `DetourAnalytics.logEvent` and
`DetourAnalytics.logRetention` still send, so you keep control over what is
reported and when. Link resolution is unchanged either way. Deferred and
runtime links resolve exactly as before.

:::note[SDK version header]
Every request the SDK sends carries an `X-SDK: react-native/<version>` header, used for diagnostics and SDK-usage analysis. It needs no configuration.
:::

## Related pages

- [Examples](https://detour.swmansion.com/docs/sdk/react-native/examples)
- [Migration to v2](https://detour.swmansion.com/docs/sdk/react-native/migration-2-0-0)
- [API Reference](https://detour.swmansion.com/docs/sdk/react-native/api-reference)
- [Custom Storage](https://detour.swmansion.com/docs/sdk/react-native/custom-storage)
- [Testing & Troubleshooting](https://detour.swmansion.com/docs/sdk/react-native/testing)