> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lucenthq.com/llms.txt
> Use this file to discover all available pages before exploring further.

# React Native SDK

> Install @lucenthq/react-native for screenshot-mode mobile session replay in React Native and Expo apps.

The Lucent React Native SDK records mobile sessions with native screenshot-mode
replay. Use it for React Native apps, Expo development builds, EAS builds, and
Expo projects that have generated native iOS or Android projects.

<Note>
  React Native replay is screenshot based. It does not use rrweb DOM or
  wireframe recording because React Native has no browser DOM.
</Note>

## Setup difficulty

| App type                    | Difficulty | What to expect                                                                                                                          |
| --------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| Bare React Native           | Easy       | Install the package, run pods for iOS, and rebuild the app.                                                                             |
| Expo with dev-client or EAS | Easy       | Install the package, generate or rebuild the native app, and run a development or production build.                                     |
| Expo Go-only                | Medium     | Expo Go cannot load Lucent's custom native modules. Move to a development build, EAS build, or prebuilt native project for full replay. |

## Install

<Tabs>
  <Tab title="Expo">
    ```bash theme={null}
    npm install @lucenthq/react-native
    npx expo install expo-dev-client
    npx expo prebuild
    npx expo run:ios
    ```

    Use `npx expo run:android` for Android, or build with EAS:

    ```bash theme={null}
    eas build --profile development --platform ios
    eas build --profile development --platform android
    ```

    See [Expo setup](/sdk/react-native/expo) for the full Expo flow.
  </Tab>

  <Tab title="Bare React Native">
    ```bash theme={null}
    npm install @lucenthq/react-native
    npx pod-install
    ```

    Then rebuild your native app:

    ```bash theme={null}
    npx react-native run-ios
    npx react-native run-android
    ```
  </Tab>
</Tabs>

## Requirements

* React 18 or newer.
* React Native 0.71 or newer.
* A Lucent public key from **Settings -> SDK keys** in the dashboard.
* A native build environment for the platforms you ship.
* Expo development builds, EAS builds, or prebuilt native projects for Expo apps.

<Warning>
  Expo Go can import the JavaScript package without crashing, but it cannot
  record full Lucent replay. Native screenshot replay, native crash capture,
  native network capture, and native log capture are disabled in Expo Go.
</Warning>

## Quickstart

Wrap your app with `LucentProvider` near the root of the React tree.

```tsx theme={null}
import { Button } from "react-native";
import {
  LucentIdentify,
  LucentProvider,
  useLucent,
} from "@lucenthq/react-native";

export function App() {
  return (
    <LucentProvider
      publicKey="luc_pk_..."
      autocapture={{ captureTouches: true }}
      options={{
        ingestBaseUrl: "https://ingest-api.lucenthq.com",
      }}
    >
      <LucentIdentify
        id={user?.id}
        email={user?.email}
        name={user?.name}
        properties={{ plan: user?.plan }}
      />
      <RootNavigator />
    </LucentProvider>
  );
}

function CheckoutButton() {
  const lucent = useLucent();

  return (
    <Button
      title="Pay"
      onPress={() => {
        void lucent.track("checkout_tapped", { plan: "pro" });
      }}
    />
  );
}
```

The provider starts recording automatically by default. Pass
`options={{ autoStart: false }}` if you need to wait for consent, then call
`lucent.start()` when recording can begin.

## Replay model

Every React Native replay batch is sent as mobile screenshot replay:

```json theme={null}
{
  "snapshotSource": "mobile",
  "snapshotLibrary": "lucent-react-native",
  "snapshotMode": "screenshot"
}
```

`snapshotMode` is not configurable in the React Native package. Browser sessions
use rrweb DOM events; React Native sessions use native iOS and Android modules
to capture screenshots, touch events, screen names, logs, network telemetry,
and crashes when enabled.

## API

The main API mirrors the browser SDK where the platform allows it.

### Provider and identity

```tsx theme={null}
import {
  LucentIdentify,
  LucentProvider,
  useLucent,
} from "@lucenthq/react-native";
```

<ResponseField name="LucentProvider" type="component">
  Creates one Lucent client for your app. Pass `publicKey`, optional `options`,
  optional `autocapture`, and your app tree as `children`.
</ResponseField>

<ResponseField name="LucentIdentify" type="component">
  Syncs identity with Lucent. React Native uses `id`, `email`, `name`, and
  `properties`.
</ResponseField>

<ResponseField name="useLucent" type="hook">
  Returns the Lucent client from the nearest provider. It must run inside
  `LucentProvider`.
</ResponseField>

### Shared tracking methods

```ts theme={null}
await lucent.start();
await lucent.track("checkout_tapped", { plan: "pro" });
await lucent.setSessionProperties({ cartValue: 129 });
await lucent.setUserProperties({ plan: "pro" });
await lucent.identify({ id: "user_123", email: "jane@example.com" });
await lucent.resetIdentity();
await lucent.captureException(error, { flow: "checkout" });
await lucent.addExceptionStep("Payment API returned 402");
await lucent.flush();
await lucent.stop();
```

### React Native methods

```ts theme={null}
await lucent.screen("Checkout", { step: "payment" });
await lucent.startRecording();
await lucent.stopRecording();

const session = await lucent.getSessionInfo();
```

<ResponseField name="lucent.screen(name, properties?)" type="Promise<void>">
  Sets the native replay screen name and emits a `$screen` event.
</ResponseField>

<ResponseField name="lucent.startRecording(resumeCurrent?)" type="Promise<void>">
  Resumes screenshot replay capture. `resumeCurrent` defaults to `true`.
</ResponseField>

<ResponseField name="lucent.stopRecording()" type="Promise<void>">
  Pauses screenshot replay capture without clearing identity.
</ResponseField>

<ResponseField name="lucent.getSessionInfo()" type="Promise<LucentSessionInfo | null>">
  Returns the current `sessionId`, `windowId`, and `recordingEnabled` state.
  In Expo Go fallback mode this returns `null`.
</ResponseField>

## Screen tracking

Track screen changes directly from a screen component:

```tsx theme={null}
import { useLucentScreen } from "@lucenthq/react-native";

export function CheckoutScreen() {
  useLucentScreen("/checkout/review", { step: "payment" });

  return <CheckoutForm />;
}
```

For React Navigation-style refs, use `useLucentNavigationTracking`. Lucent does
not add a navigation peer dependency.

```tsx theme={null}
import { NavigationContainer, useNavigationContainerRef } from "@react-navigation/native";
import { useLucentNavigationTracking } from "@lucenthq/react-native";

function LucentNavigationTracker({ navigationRef }) {
  useLucentNavigationTracking(navigationRef, {
    properties: (route) => ({
      routeKey: route.key,
      path: route.path,
    }),
  });

  return null;
}

export function RootNavigator() {
  const navigationRef = useNavigationContainerRef();

  return (
    <NavigationContainer ref={navigationRef}>
      <LucentNavigationTracker navigationRef={navigationRef} />
      {/* screens */}
    </NavigationContainer>
  );
}
```

## Processing controls

Processing controls decide which mobile sessions are recorded, processed, and
analyzed. Filters and sampling usually control whether a session is analyzed,
not whether the app can send events at all. React Native replay batches can
still be accepted by Lucent even when a session later does not match your
processing rules.

Lucent dashboard filters are configured per project and apply to the current
project selected in the app header.

### Required URL and screen filters

Lucent Required URL filters are fail-closed. If the selected project has
Required URL patterns, Lucent evaluates them before full AI analysis:

* Sessions with matching URL or screen evidence are processed.
* Sessions without matching evidence are skipped from analysis as
  `required_url_missing`.
* Skipped sessions may still exist as ingested SDK replay data, depending on
  the product surface and session status.

For React Native and Expo sessions, Lucent builds URL evidence from screen and
route metadata. The SDK records path-style mobile URLs with the
`lucent-react-native` scheme so URL filters can match mobile screens the same
way they match web paths.

Use path-style screen names when you want mobile sessions to match path-style
filters. For example, a Required URL filter of `/onboarding` works reliably
when the app records `/onboarding` as screen evidence.

| Lucent filter                   | Recommended SDK call                                     | Notes                                                                                 |
| ------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| Required URL `/onboarding`      | `useLucentScreen("/onboarding")`                         | Best for a screen component.                                                          |
| Required URL `/checkout/review` | `lucent.screen("/checkout/review", { step: "payment" })` | Best from a router listener or custom navigation handler.                             |
| Required URL `/onboarding`      | Avoid relying on `lucent.screen("Onboarding")`           | `Onboarding` is a human screen label, not a URL path. It may not match `/onboarding`. |

```tsx theme={null}
import { useLucentScreen } from "@lucenthq/react-native";

export function OnboardingScreen() {
  useLucentScreen("/onboarding");

  return <OnboardingFlow />;
}
```

```ts theme={null}
await lucent.screen("/checkout/review", {
  step: "payment",
});
```

Human labels such as `"Onboarding"` are fine for analytics, but do not rely on
them when the Lucent filter is path-like, such as `/onboarding`.

For React Navigation, matching is strongest when the route evidence is
path-like. Prefer route paths such as `/onboarding` or `/checkout/review` over
display names such as `Onboarding`.

```tsx theme={null}
function LucentNavigationTracker({ navigationRef }) {
  useLucentNavigationTracking(navigationRef, {
    properties: (route) => ({
      path: route.path,
    }),
  });

  return null;
}
```

### Excluded URL and domain filters

Excluded URL and domain filters skip sessions only when Lucent observes screen
or URL evidence that matches the excluded patterns. The same React Native path
caveat applies: use path-style screen names if you expect path-style filters to
match mobile sessions.

```tsx theme={null}
// Matches an excluded URL filter such as /internal-tools.
useLucentScreen("/internal-tools");
```

### User, email, and anonymous-user controls

User, email, and domain filters depend on identity metadata. Identify users as
soon as the app knows who they are, ideally before the important flow begins.
Anonymous sessions may not match user, email, or email-domain filters.

```tsx theme={null}
<LucentIdentify
  id={user.id}
  email={user.email}
  name={user.name}
  properties={{ plan: user.plan }}
/>
```

If **Ignore Anonymous Users** is enabled for the selected project, Lucent may
skip anonymous SDK sessions from that project. Call `identify` as soon as the
user is known so their mobile session is eligible for user-aware filters and
processing rules.

### Property filters

User and session properties are filterable metadata where configured. Set them
before important flows so the properties are present when Lucent evaluates the
session.

```ts theme={null}
await lucent.setUserProperties({
  plan: "pro",
  role: "admin",
});

await lucent.setSessionProperties({
  checkoutVariant: "new-flow",
  entryPoint: "push-notification",
});
```

### Sampling, event triggers, and minimum duration

You should not expect every mobile app launch to produce an analyzed session.
Local SDK sampling and remote-config sampling can prevent a session from being
recorded or processed.

```tsx theme={null}
<LucentProvider
  publicKey="luc_pk_..."
  options={{
    sampling: { sessionReplay: 0.25 },
    remoteConfig: true,
  }}
>
  <RootNavigator />
</LucentProvider>
```

If replay is configured to start only after certain events, activity before the
trigger may not have replay data. This is useful when you only want replay after
an important flow begins.

```tsx theme={null}
<LucentProvider
  publicKey="luc_pk_..."
  options={{
    replay: {
      eventTriggers: ["checkout_started"],
    },
  }}
>
  <RootNavigator />
</LucentProvider>
```

```ts theme={null}
await lucent.track("checkout_started");
```

Very short sessions may also be dropped or skipped depending on
`replay.minimumDurationMs`. If you are testing with tiny sessions, keep the app
open long enough to exceed your minimum duration and allow a flush.

### Privacy masking

Lucent masks sensitive mobile content before screenshots leave the device.
Input fields, images, and sandboxed system views are masked by default.

```tsx theme={null}
<LucentProvider
  publicKey="luc_pk_..."
  options={{
    privacy: {
      maskAllInputs: true,
      maskAllImages: true,
      maskSandboxedViews: true,
    },
  }}
>
  <RootNavigator />
</LucentProvider>
```

### Capture toggles

Lucent can capture console logs, network telemetry, app lifecycle events,
JavaScript errors, unhandled promise rejections, and native crashes. Disable
individual capture types when they do not fit your privacy or telemetry policy.

```tsx theme={null}
<LucentProvider
  publicKey="luc_pk_..."
  options={{
    capture: {
      console: true,
      network: true,
      appLifecycle: true,
      errors: true,
      unhandledRejections: true,
      nativeCrashes: true,
    },
  }}
>
  <RootNavigator />
</LucentProvider>
```

Expo Go cannot load Lucent's native module, so native crash, network, and log
capture do not behave like they do in a development client, EAS build, or
prebuilt native app.

### Offline and batching behavior

Mobile sessions are batched and flushed periodically. If the app is offline or
backgrounded, events may be queued and flushed later. Do not expect every
mobile session to appear instantly in the dashboard.

```tsx theme={null}
<LucentProvider
  publicKey="luc_pk_..."
  options={{
    batching: {
      flushIntervalMs: 10000,
      maxEventsPerBatch: 50,
      maxBatchBytes: 1048576,
    },
  }}
>
  <RootNavigator />
</LucentProvider>
```

### Source maps and release metadata

Set release metadata and upload source maps for readable React Native
JavaScript stack traces. For Expo and EAS builds, configure the optional Expo
plugin, Metro helper, and build environment variables described in
[Expo source-map tooling](/sdk/react-native/expo#source-map-and-release-metadata).

```tsx theme={null}
<LucentProvider
  publicKey="luc_pk_..."
  options={{
    metadata: {
      app: "Acme Mobile",
      namespace: "com.acme.mobile",
      version: "1.2.3",
      build: "123",
      release: "1.2.3+123",
    },
  }}
>
  <RootNavigator />
</LucentProvider>
```

## Autocapture and errors

Enable touch autocapture by passing `autocapture`. Lucent wraps your app in a
responder-capture view and emits `$touch` events alongside replay screenshots.

```tsx theme={null}
<LucentProvider publicKey="luc_pk_..." autocapture={{ captureTouches: true }}>
  <RootNavigator />
</LucentProvider>
```

Unhandled JavaScript errors, unhandled promise rejections, and native uncaught
exceptions are captured by default when the SDK starts. Native crashes are
stored synchronously and emitted as `$native_crash` on the next launch.

```tsx theme={null}
import { LucentErrorBoundary } from "@lucenthq/react-native";

<LucentProvider publicKey="luc_pk_...">
  <LucentErrorBoundary>
    <RootNavigator />
  </LucentErrorBoundary>
</LucentProvider>
```

Disable capture hooks when needed:

```tsx theme={null}
<LucentProvider
  publicKey="luc_pk_..."
  options={{
    capture: {
      console: false,
      network: false,
      errors: false,
      unhandledRejections: false,
      nativeCrashes: false,
    },
  }}
>
  <RootNavigator />
</LucentProvider>
```

## Configuration

```tsx theme={null}
<LucentProvider
  publicKey="luc_pk_..."
  options={{
    ingestBaseUrl: "https://ingest-api.lucenthq.com",
    sampling: { sessionReplay: 1.0 },
    privacy: {
      maskAllInputs: true,
      maskAllImages: true,
      maskSandboxedViews: true,
    },
    capture: {
      console: true,
      network: true,
      appLifecycle: true,
      errors: true,
      unhandledRejections: true,
      nativeCrashes: true,
    },
    remoteConfig: true,
    replay: {
      screenshotThrottleMs: 1000,
      minimumDurationMs: 3000,
      screenshotBackgroundCapture: false,
      eventTriggers: ["checkout_tapped"],
    },
    metadata: {
      app: "Acme Mobile",
      namespace: "com.acme.mobile",
      version: "1.2.3",
      build: "123",
      release: "1.2.3+123",
      properties: {
        channel: "production",
      },
    },
  }}
>
  <RootNavigator />
</LucentProvider>
```

Remote config is enabled by default. When it is enabled, native replay waits for
Lucent's init response before auto-starting recording and falls back to local
config if the request fails.

## Optional Expo and Metro tooling

No Expo config plugin is required for replay capture. The native module
autolinks during React Native linking or Expo prebuild/dev-client/EAS native
project generation.

Use the optional Expo config plugin and Metro helper only when you want Lucent
production source-map metadata and upload hooks.

```json theme={null}
{
  "expo": {
    "plugins": [["@lucenthq/react-native/expo", { "skipOnConflict": true }]]
  }
}
```

```js theme={null}
const { getDefaultConfig } = require("expo/metro-config");
const { withLucentMetroConfig } = require("@lucenthq/react-native/metro");

const config = getDefaultConfig(__dirname);

module.exports = withLucentMetroConfig(config, {
  release: process.env.LUCENT_RELEASE,
});
```

See [Expo setup](/sdk/react-native/expo) for the complete Expo path.

## Troubleshooting

<AccordionGroup>
  <Accordion title="I only see Expo Go fallback or no sessions">
    Expo Go cannot load Lucent's native module. Use an Expo development build,
    EAS build, or prebuilt native project via `npx expo run:ios` or
    `npx expo run:android`. In Expo Go, provider/hooks/API calls are safe, but
    `getSessionInfo()` returns `null` and no native replay batches are sent.
  </Accordion>

  <Accordion title="Native module not linked">
    Rebuild the native app after installing the package. For Expo, run
    `npx expo prebuild` and then `npx expo run:ios`, `npx expo run:android`,
    or an EAS build. For bare React Native, run `npx pod-install` for iOS and
    rebuild both native targets.
  </Accordion>

  <Accordion title="iOS pods or CocoaPods fail">
    Run `npx pod-install` from the app root. If pods are stale, delete
    `ios/Pods` and `ios/Podfile.lock`, then run `npx pod-install` again. Make
    sure Xcode command-line tools and CocoaPods are installed before rebuilding.
  </Accordion>

  <Accordion title="Android SDK, Gradle, or JDK is missing">
    Install Android Studio command-line tools, a JDK supported by your React
    Native version, and Gradle or the app's `android/gradlew` wrapper. Set
    `ANDROID_HOME` or `ANDROID_SDK_ROOT` if your SDK is not in the default
    location.
  </Accordion>

  <Accordion title="No replay batches are arriving">
    Confirm you are running a native build, not Expo Go. Check that the public
    key starts with `luc_pk_`, `ingestBaseUrl` is reachable, and
    `sampling.sessionReplay` is greater than `0`. If you configured
    `replay.eventTriggers`, replay waits until one of those events is tracked.
    If you configured `replay.minimumDurationMs`, very short sessions may be
    dropped.
  </Accordion>

  <Accordion title="My session ingested but was skipped from analysis">
    Check whether the selected project has Required URL filters. Required URL
    filtering applies to processing eligibility, not ingestion. If the filter is
    `/onboarding`, use path-style screen evidence such as
    `useLucentScreen("/onboarding")` instead of relying on a display label such
    as `Onboarding`.
  </Accordion>

  <Accordion title="Verifying events with track()">
    Add a temporary button that calls `lucent.track("lucent_mobile_test")`,
    interact with the app in a native build, then open **Sessions** in the
    Lucent dashboard. Call `await lucent.getSessionInfo()` from inside the app
    to confirm the SDK has an active session.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Expo setup" icon="mobile" href="/sdk/react-native/expo">
    Configure Lucent in Expo development builds, EAS builds, and prebuilt apps.
  </Card>

  <Card title="Session replay integrations" icon="video" href="/integrations/session-replay">
    See how Lucent handles web, mobile, screenshot, and wireframe replay data.
  </Card>
</CardGroup>
