Skip to main content
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.
  • 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.
Terminal
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):
index.js
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:
index.js
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. 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 (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:
app/_layout.tsx
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; 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 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 and 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.

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.
  • 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. 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