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

# Instrument your mobile app

> Instrument a React Native or Expo app with @sazabi/react-native to capture errors, screens, touches, network activity, and console output, correlated with your backend logs.

**Early preview**

The React Native SDK is an early preview. Options, event attributes, and
capture behavior may change between releases, so pin the version you install
and read the changelog before you upgrade.

You can send mobile telemetry (errors, screen changes, touches, network activity, and console output) to the same Sazabi intake your backend uses. The Sazabi React Native SDK (`@sazabi/react-native`) captures a semantic event stream and injects W3C trace context into outgoing requests so a symptom in the app correlates with your backend logs. By the end of this guide you will have the SDK initialized and confirmed events arriving. Setup takes a few minutes plus a build.

## What the React Native SDK captures

* **Errors and console**: errors that reach React Native's global error handler, errors thrown while React renders inside `<SazabiRoot>`, and unhandled promise rejections on Hermes, plus `console.error` and `console.warn` output mirrored as log events. `app.error.kind` carries React Native's own `isFatal` flag as `fatal` or `uncaught`, and each error is one row: the `console.error` line React Native prints for it is not mirrored again. With a storage adapter, a fatal error's events are written to an outbox and replayed on the next launch after retryable failures. Permanent 4xx failures are dropped with a warning; see [Delivery](https://docs.sazabi.com/reference/react-native-sdk.md#delivery).
* **Network**: `fetch` and `XMLHttpRequest` requests other than intake uploads, Metro dev-server traffic, and `/symbolicate` calls in development, with a request-id response header attached as a join key against platform logs when present.
* **Touches**: one episode per touch, with taps and rapid repeat taps derived from them. The SDK records developer-authored names (`accessibilityLabel`, `testID`), never child text.
* **Screens**: route changes from your React Navigation or expo-router container, with param keys but not param values.
* **Session identity**: a session id that outlives launches when a storage adapter is set, a per-launch id, and a client-asserted distinct id you set with `identify()`.

The SDK sees the JavaScript side of the app only. Native crashes, ANRs, app-start metrics, and session replay are not captured.

## Set up the SDK

Install the package, plus AsyncStorage if your app does not have it yet. AsyncStorage is the optional `storage` adapter used below; it keeps the session across launches and turns on the crash outbox.

```bash title="Terminal" theme={"system"}
npm install @sazabi/react-native @react-native-async-storage/async-storage
# Expo: npx expo install @sazabi/react-native @react-native-async-storage/async-storage
```

Then import `@sazabi/react-native/register` as the first import of your entry module and call `init()` once. The entry module is the file your `package.json` `main` field points to: `index.js` in a React Native CLI app, `index.ts` in an Expo app. A React Native CLI app has no `process.env` at runtime, so use a constant (or [`react-native-config`](https://github.com/lugg/react-native-config)):

```js title="index.js" theme={"system"}
import "@sazabi/react-native/register";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { init } from "@sazabi/react-native";
import { AppRegistry } from "react-native";
import App from "./App";
import { name as appName } from "./app.json";

// Or `Config.SAZABI_INTAKE_URL` with react-native-config.
const SAZABI_INTAKE_URL = "https://<your-intake-host>.intake.sazabi.com";

init({
  intakeUrl: SAZABI_INTAKE_URL,
  serviceName: "mobile-app",
  storage: AsyncStorage,
});

AppRegistry.registerComponent(appName, () => App);
```

In an Expo app, read the URL from an `EXPO_PUBLIC_*` variable, which Expo inlines at build time. With expo-router, `main` is `expo-router/entry` and `app/_layout.tsx` loads after every screen module, so it is too late for the register import. Add an `index.js` that imports the SDK first and expo-router last, and set `"main": "index.js"` in `package.json`:

```js title="index.js" theme={"system"}
import "@sazabi/react-native/register";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { init } from "@sazabi/react-native";
import "expo-router/entry";

init({
  intakeUrl: process.env.EXPO_PUBLIC_SAZABI_INTAKE_URL,
  serviceName: "mobile-app",
  storage: AsyncStorage,
});
```

AsyncStorage returns promises, so a fatal error's outbox write may not finish before the process dies. The SDK also sends the batch at once, so a crash can arrive twice; every row of one crash batch carries the same `app.outbox.id` to deduplicate on. A synchronous store such as an MMKV wrapper completes the write before the process ends and waits until the next launch to send the crash. Upload retries can still produce duplicate deliveries: delivery is at least once, and a crash whose acknowledgement the app never saw is posted again on the next launch under the same id. A crash written to its own key but never listed, because the process died between the two writes, is recovered on the next launch when the adapter provides `getAllKeys`, as AsyncStorage does; see [Crash outbox](https://docs.sazabi.com/reference/react-native-sdk.md#crash-outbox).

Like every other log source, the SDK is configured with your intake URL alone. Copy it from the Sazabi React Native SDK source under [**Settings > Log streams**](https://app.sazabi.com/settings/log-source-providers) (create the source there if your project has none, or with `sazabi log-sources create sazabi_react_native_sdk --mode manual`). The URL embeds a write-only public key, which is why it is safe to ship in an app bundle.

Then wrap your app in `<SazabiRoot>` and pass your navigation container ref to `trackNavigation()` so touches, screens, and render errors are recorded. `<SazabiRoot>` must be the outermost component, outside your own error boundaries and providers: it records an error thrown while React renders as `fatal`, then rethrows it, so the app still crashes as it would without the SDK. Under expo-router, do this in `app/_layout.tsx` with the ref from expo-router's `useNavigationContainerRef`:

```tsx title="app/_layout.tsx" theme={"system"}
import { useEffect } from "react";
import { Stack, useNavigationContainerRef } from "expo-router";
import { SazabiRoot, trackNavigation } from "@sazabi/react-native";

export default function RootLayout() {
  const navigationRef = useNavigationContainerRef();
  useEffect(() => trackNavigation(navigationRef), [navigationRef]);
  return (
    <SazabiRoot>
      <Stack />
    </SazabiRoot>
  );
}
```

`init()` takes two required options, `intakeUrl` and `serviceName`, plus optional ones such as `storage`, `serviceVersion`, `environment`, `consent`, `network`, `pointer`, `console`, and `errors`. For every option, method signature, and the exact call semantics, including the React Navigation setup and the `sazabiName` and `sazabiMask` props for naming touches, see the **[React Native SDK reference](https://docs.sazabi.com/reference/react-native-sdk.md)**; this guide does not repeat them.

## Correlate with backend logs

By default the SDK injects a W3C `traceparent` header into every request the app makes and stamps the record trace id on the resulting network event. Intake uploads are excluded; in development, Metro dev-server traffic and URLs containing `/symbolicate` are also excluded from capture and propagation. Mobile has no browser origin or CORS preflight, so propagation defaults to all other requests; the browser SDK defaults to same-origin requests. When your backend also sends logs to Sazabi with the same trace context, the agent can line up a tap in the app with the backend work it triggered.

Narrow the injection with `init()`'s `network.allowlist` option when a third-party API must not see the header (for example `network: { allowlist: ["https://api.example.com"] }`). A string is a host (`api.example.com`), a domain with its subdomains (`.example.com`), or an origin with an optional path prefix; it matches by origin or host, never as a substring, so a lookalike host gets no header. RegExps test the full URL. Passing a list replaces the default, so only matching requests get the header. The allowlist controls propagation, not capture; `network.propagateTraceContext: false` disables injection while keeping network events.

Network events keep the request URL in `url.full` with the values of sensitive query parameters (tokens, OAuth codes, API keys, presigned-URL signatures) replaced by `[redacted]`. Add your own parameter names with `network.redactQueryParams`; see [Redacted URLs](https://docs.sazabi.com/reference/react-native-sdk.md#redacted-urls) for the built-in list.

## Delivery and storage limits

The SDK retries 408, 429, 5xx, network failures, and 30-second timeouts with backoff and `Retry-After`. A 413 splits the batch; an event that still cannot fit is dropped. Other 4xx responses drop the batch with one warning per status per launch. Stored crash reports keep retryable failures for a later launch, but a 413 deletes the stored report without splitting it. Correct a rejected intake URL before generating more events.

The offline queue has a 500-event default and a 1 MiB cap. The crash outbox keeps up to 20 batches within a 512 K-character budget (524,288 UTF-16 code units). Both discard the oldest first; one oversized crash batch can exceed the character budget. The next delivered row after an in-memory queue eviction or refused queued batch carries `app.sdk.dropped_events`. For the retry timing and storage caveats, see [Delivery](https://docs.sazabi.com/reference/react-native-sdk.md#delivery) and [Crash outbox](https://docs.sazabi.com/reference/react-native-sdk.md#crash-outbox).

With a storage adapter, the SDK also saves the queue each time the app leaves the foreground, because the OS can kill a backgrounded app without warning. The copy is removed once everything queued is delivered; otherwise the next launch posts it. A stored crash entry that is not valid JSON is discarded with one warning and removed on that launch. The SDK writes under keys starting with `sazabi.`; see [Storage keys](https://docs.sazabi.com/reference/react-native-sdk.md#storage-keys).

## Verify

Confirm real mobile events reach your project.

1. Build or run the app with `init()` active, then launch it on a device or simulator.
2. Trigger an event the SDK captures: tap a button, navigate to another screen, then force a `console.error("sazabi react native sdk test")` from your app.
3. Filter to `service.name` equal to the `serviceName` you configured (`mobile-app` in the examples above). In a thread, ask the Sazabi agent for logs from that service, or run `sazabi logs ask "logs from mobile-app" --since 1h`.
4. You should see records with `app.event_type` values such as `click`, `pointer`, `navigation`, `error`, or `network`, with `os.name` on the resource naming `ios` or `android`. Events flush every 5 seconds by default (and when the app goes to the background), so they usually appear within about 10 seconds of the interaction.

If nothing appears, check that you pasted the complete intake URL from the source's setup screen and that the Metro console shows no `[@sazabi/react-native]` warning.

## Troubleshooting

* **No events at all.** Confirm `init()` runs in your entry file, that both required options are set, that the app is not running under react-native-web (where this SDK steps aside for the browser SDK), and that no `consent` gate is holding capture dormant.
* **Requests rejected.** Confirm `intakeUrl` is the complete intake URL copied from the source's setup screen; a partial or hand-built URL is rejected.
* **No touches or screens.** Wrap the root in `<SazabiRoot>` and call `trackNavigation()` with your container ref.
* **A fatal error's events never arrived.** Without a `storage` adapter there is no crash outbox. With AsyncStorage or another promise-returning adapter, the write may not finish before the process dies and the immediate send may fail. Events that were written are replayed on the next launch. A render error is recorded only when it is thrown inside `<SazabiRoot>`.
* **Unhandled rejections are missing.** Rejection tracking needs the Hermes engine; an app on JavaScriptCore gets no rejection events. On Hermes, a crash reporter initialized after `init()` took the engine's single rejection-tracker slot. Initialize the SDK last, or hand the other reporter's capture call to the SDK with `errors.previousRejectionTracker`; see [Other crash reporters](https://docs.sazabi.com/reference/react-native-sdk.md#other-crash-reporters).
* **Another crash reporter stopped seeing rejections.** `init()` took the slot. Pass the reporter's capture call as `errors.previousRejectionTracker`, or set `errors: { unhandledRejections: false }` to leave the slot to it. A development build prints one `[@sazabi/react-native]` note when the slot is taken with neither option set.
* **Jest fails on an ES module.** Include the SDK in your `transformIgnorePatterns` exception; see [Jest](https://docs.sazabi.com/reference/react-native-sdk.md#jest). Metro resolves the register entry through the package's `react-native` field even when package exports are disabled.
* **Two copies of the SDK in one bundle.** Deduplicate the dependency and import `init` and `SazabiRoot` from the same copy. Initialization is shared, but capture components from the other copy can miss events.
* **Backend correlation missing.** Ensure your backend forwards logs with the incoming trace context.

## Further reading

> [card] [React Native SDK reference](https://docs.sazabi.com/reference/react-native-sdk.md)
>
> Every init option, capture type, and API.

> [card] [Sazabi React Native SDK log source](https://docs.sazabi.com/catalogs/log-sources/send-to-an-endpoint/sazabi-react-native-sdk.md)
>
> The catalog page with dashboard and CLI setup.

> [card] [Instrument your frontend](https://docs.sazabi.com/guides/instrument/frontend.md)
>
> The same setup for a web app.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.