# Usage

## Initialize provider in app root

```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 DetourProvider per app]
Use exactly one `DetourProvider` in your app tree.

On Expo Web, `DetourProvider` is disabled:
it returns a no-op context (`isLinkProcessed: true`, `link: null`) and does not process links.
:::

If you want complete, runnable integrations for each navigation style, go to [Examples](https://detour.swmansion.com/docs/sdk/react-native/examples).

## Handle resolved links with `useDetourContext()`

```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`):

<PropertyList>
  <Property name="link.route">
    Full in-app route with query string (e.g. `/details?utm=1`).
  </Property>
  <Property name="link.pathname">
    Path without query (e.g. `/details`).
  </Property>
  <Property name="link.params">
    Parsed query params record.
  </Property>
  <Property name="link.url">
    Original URL or raw value.
  </Property>
  <Property name="link.type" type={"'deferred' | 'verified' | 'scheme'"} />
</PropertyList>

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

## Choose `linkProcessingMode`

`Config.linkProcessingMode` controls which link sources are handled by the SDK:

| 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

When a Universal Link or App Link is processed, the SDK verifies the click against your plan's click limit before delivering the resolved link. While your app is within the limit, links resolve normally. When the limit is exceeded, the link is **not** delivered:

- With `DetourProvider`, `link` stays `null` and `useDetourContext()` performs no navigation.
- With `createDetourNativeIntentHandler` in resolve mode, the handler returns `fallbackPath`.

In both cases the SDK logs a `🔗[Detour:CLICK_LIMIT_ERROR]` message with the limit details (`clicksInPeriod`, `effectiveLimit`).

This dedicated `🔗[Detour:CLICK_LIMIT_ERROR]` flow applies only to Universal/App links. Deferred-link matching and short-link resolution are subject to the same plan limits on the backend, but the SDK does not surface a dedicated limit error for them. When the limit is exceeded the affected link fails to resolve (it returns `null`) instead of logging a click-limit error. Custom scheme links are handled locally and are not limit-checked.

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

:::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 from the backend.
:::

## Expo Router integration

:::tip[Recommended for Expo apps]
If you use Expo Router, we **strongly recommend** using our dedicated `createDetourNativeIntentHandler` API in a [`+native-intent.tsx`](https://docs.expo.dev/router/advanced/native-intent/) file. This intercepts URLs at the router level before any screen renders, avoiding flash-of-wrong-screen issues and providing the smoothest deep-linking experience.
:::

Expo Router's `+native-intent.tsx` file lets you intercept and transform incoming system URLs before the router processes them. The Detour SDK provides `createDetourNativeIntentHandler`, a factory that creates 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
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 caught and the user is routed to the home screen (default `fallbackPath`). Deferred links are still handled separately by `DetourProvider`.
:::

### Resolve mode

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

```ts
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.).

### 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 includes examples for common native intent scenarios:

- **`expo-router-native-intent`** — Full resolve mode setup with a custom `mapToRoute` function and scheme URL handling. Use this as a starting point when you need to transform resolved URLs before routing (e.g. stripping path prefixes) or when you want to handle custom scheme links alongside Detour links.

- **`expo-router-advanced`** — Demonstrates Detour inside an auth- and onboarding-gated flow. Deferred and runtime links are preserved through sign-in and one-time onboarding before navigating to their destination. Use this when your app requires the user to be signed in (and optionally onboarded) before a deep link should take effect.

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

### Alternative: catch-all route without `+native-intent.tsx`

If you prefer not to use `+native-intent.tsx`, you can handle all incoming links through `DetourProvider` with `linkProcessingMode: 'all'` (the default) and implement a catch-all route in your navigation stack.

In this approach, `DetourProvider` processes all link types (deferred, Universal/App Links, and custom scheme). You consume the resolved link via `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 introduced native intent support (pre-1.x). It still works but the `+native-intent.tsx` approach provides better UX by resolving links before the first screen renders.

## Analytics module

`DetourAnalytics` emits events that are sent by the active `DetourProvider`.

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

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

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

### Automatic events

Besides the events you log manually, `DetourProvider` reports some events on its own, with no wiring needed:

- `app_open` — a retention event logged once per session when the provider first mounts (cold start).

Universal Link and App Link opens are no longer emitted as a client event. They are recorded by the backend through a dedicated click endpoint (see [Click limits](#click-limits)).

:::note[SDK version header]
Every request the SDK sends to the Detour backend carries an `X-SDK: react-native/<version>` header. It is used for diagnostics and SDK-usage analysis and requires 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)