ReadyNative
Crash reportingPro

Sentry

Set up Sentry for crash reporting in an Expo app with ReadyNative: JS + native crash reporting · Expo Go OK (JS errors only)

Pro

Expo Go: yes

Runs in Expo Go; no dev build needed for this module.

This reports crashes and handled errors to Sentry: src/lib/crash.ts calls Sentry.init once crash consent is given (only when the DSN is set, with environment from APP_VARIANT, errors only, no traces), crash.capture(err, ctx) maps to captureException with ctx as extra, and CrashBoundary is a Sentry.ErrorBoundary whose fallback is the kit's ErrorState with Retry. CrashProvider (order 40) mounts that boundary app-wide, with its own ThemeProvider since it sits above the ui provider. If you set up with --with-examples there's an example at /examples/crash and tests for both the capture path and the boundary. @/lib/crash keeps the same API whichever option you pick - Sentry is the one service option here, and it's what you want the moment real users can hit a bug you can't reproduce.

Setup

bun run setup --crash sentry
  1. Create a project at sentry.io (platform: React Native), then open [Settings] → [Projects] → your project → [Client Keys (DSN)] and copy the DSN into .env as EXPO_PUBLIC_SENTRY_DSN. It's public and safe in the client. Without it the SDK isn't initialised, crash.capture only logs in dev, and the example shows "Configure EXPO_PUBLIC_SENTRY_DSN".
  2. Take the org and project slugs from the project URL and set them as the build-time secrets SENTRY_ORG and SENTRY_PROJECT - never with an EXPO_PUBLIC_ prefix.
  3. Create a token at [Settings] → [Auth Tokens] with the scopes project:releases and org:read, and set it as SENTRY_AUTH_TOKEN. These three are only used at build time, to upload source maps.
  4. If you install with bun, run bun pm trust @sentry/cli once on an existing tree. bun blocks lifecycle scripts by default and @sentry/cli downloads its binary in postinstall; without it, source-map uploads fail. The module adds trustedDependencies: ["@sentry/cli"] to package.json, so a fresh bun install handles it for you.
  5. The config plugin ["@sentry/react-native", { organization: "", project: "", url }] is added to .readynative.json and applied by app.config.ts. The empty org and project make the plugin fall back to the SENTRY_ORG and SENTRY_PROJECT env vars at build time, so there's nothing to edit. (The plugin id is the package name, not @sentry/react-native/expo: same code, and expo install would otherwise try to add it to the dynamic config and fail.)

Going to production?

Put SENTRY_ORG, SENTRY_PROJECT and SENTRY_AUTH_TOKEN in EAS (eas env:set --environment production --visibility sensitive) so release builds and eas update can upload source maps - without them, stack traces stay minified. Use a separate Sentry project (or at least rely on the environment tag, which follows APP_VARIANT) so production issues aren't buried in your dev noise.

  1. Run bun run doctor - every row for this module should be green.

The keys, in one place:

KeyWhere to get it
EXPO_PUBLIC_SENTRY_DSNhttps://sentry.io/settings/projects/ → project → Client Keys (DSN). Public; safe in the client.
SENTRY_ORG, SENTRY_PROJECT, SENTRY_AUTH_TOKEN (EAS secrets, never EXPO_PUBLIC_)org/project slugs from the project URL; token from https://sentry.io/settings/auth-tokens/ (scopes project:releases, org:read). Build time only, for source-map upload.

Dashboards: Issues at https://sentry.io/organizations/<org>/issues/ · Releases (source maps) at …/releases/ · Client Keys. Deps: @sentry/react-native ~7.11.0 - the version Expo SDK 57 pins, so 8.x isn't an option until that pin moves (npx expo install --fix downgrades anything newer).

Usage

Report a handled error with context:

import { crash } from "@/lib/crash";

crash.capture(err, { screen: "checkout", orderId });

Wrap a risky subtree so a render error doesn't take the app down:

import { CrashBoundary } from "@/lib/crash";

<CrashBoundary>{riskySubtree}</CrashBoundary>;

Tag the signed-in user, so issues say who hit them:

import * as Sentry from "@sentry/react-native";

Sentry.setUser({ id: user.id });

Sentry.init runs only once consent.get().crash is true (and the DSN is set). Until then no JS or native crash report, session or client report leaves the device - native crash files from a previous run wait for the next init. Withdrawing consent calls Sentry.close(); consent again runs a new Sentry.init. beforeSend / beforeSendTransaction still return null while crash consent is false, and crash.capture is a no-op then. With consent/consent that is the EU/EEA, UK, CH, CA, BR until the sheet is answered; the Settings → Privacy toggle takes effect live. isCrashConfigured only means the DSN is set; isCrashRunning() tells you whether the SDK is initialised.

Everything that does go out passes scrubEvent(): user.ip_address, user.email, user.username, request.headers, request.cookies, request.query_string and request.data are dropped, the query string is stripped from request.url, and emails ([email]), JWTs, Bearer … tokens and token= / password= / api_key= / secret= values are masked in the message, logentry, exception values, extra (secret-named keys become [redacted]) and breadcrumbs. beforeBreadcrumb (scrubBreadcrumb) strips query strings and fragments from data.url, data.from and data.to on http and navigation breadcrumbs. user.id stays so you can count affected users. sendDefaultPii is off. ctx is scrubbed too, but still keep crash.capture(err, ctx) contexts free of personal data.

In Sentry, turn on [Project Settings] → [Security & Privacy] → "Prevent Storing of IP Addresses": the connection still reveals the device IP to Sentry, which also derives geo data from it.

Gotchas

  • There's no Metro plugin (withSentryConfig in metro.config.js, which the ui module owns), so JS bundles carry no Sentry Debug IDs and uploads match by release and dist instead. Run npx sentry-expo-upload-sourcemaps dist after eas update; EAS native builds upload through the plugin's gradle and xcode scripts. Add the Metro plugin yourself if you want Debug IDs.
  • Sentry.wrap(RootLayout) - touch breadcrumbs and the profiler - isn't applied, because core owns _layout.tsx. Wrap it there if you want them.
  • bun run setup --crash none removes everything; with --with-examples, /examples/crash then shows "Module not installed".
  • Expo Go covers JS errors, breadcrumbs and the boundary. Native crashes, offline caching and Sentry.nativeCrash() need a dev build.

Check it works (the Examples steps need a tree set up with --with-examples):

  1. Put a real EXPO_PUBLIC_SENTRY_DSN in .env, run bun run start -- -c, and open the app on a device (Expo Go is fine for JS errors).
  2. Examples → "Crash reporting" → Capture test error: Issues shows Error: Test error from /examples/crash with extra.source = examples/crash within about a minute, environment dev.
  3. Tap Throw render error: the card shows "Something went wrong · Render error from /examples/crash" with Retry, the rest of the screen still works, and a second issue appears with mechanism: react-error-boundary. Tap Disarm, then Retry, and "Renders fine until you throw." is back.
  4. On a dev build only, add Sentry.nativeCrash() to a button and relaunch: a native crash issue appears (symbolication needs SENTRY_AUTH_TOKEN).
  5. Remove the DSN and restart: the example shows "Configure EXPO_PUBLIC_SENTRY_DSN" and the app still boots.

Remove it

While modules/ exists (a tree set up with --keep-modules), setup does all of it:

bun run setup --crash none --yes --keep-modules

In a finalized tree setup is a stub, so you undo it by hand. Here is everything this module added:

  1. Delete the files that are still there (the demo screens are gone already unless you set up with --with-examples): src/app/examples/crash.tsx, src/lib/__tests__/crash-boundary.test.tsx, src/lib/__tests__/crash-sentry.test.ts, src/screens/examples/crash-example-screen.tsx.
  2. Replace, don't delete src/lib/crash.ts: core code imports it, so swap in the no-op version from modules/crash/none/files/ of a fresh clone of your tier repo - same exports, nothing behind them.
  3. Uninstall the dependencies: bun remove @sentry/react-native.
  4. Drop the config plugin @sentry/react-native from .readynative.json → modules.app.expo.plugins (that is where app.config.ts reads it from), then rebuild the dev build.
  5. Unwrap the provider: delete <CrashProvider> and its import from src/providers.tsx.
  6. Remove the env keys EXPO_PUBLIC_SENTRY_DSN from .env, .env.example and your EAS environment, and EXPO_PUBLIC_SENTRY_DSN from src/lib/env.ts.
  7. Update the privacy declarations: remove this module's entries from .readynative.json → modules.app.expo.ios.privacyManifests, then re-run bun run gen:privacy and revise your store privacy answers.
  8. Check it: bun run typecheck and bun run lint point at anything that still imports the removed files; bun run gen:graph refreshes docs/ARCHITECTURE.md.

Reference

Everything below is generated from modules/crash/sentry/module.json - the same file bun run setup reads, so it is what actually lands in your repo.

Install

bun run setup --crash sentry

Module id: crash/sentry (the default for this category).

Dependencies

PackageVersionKind
@sentry/react-native~7.11.0dependency (expo install)

Config plugins

Merged into app.config.ts through .readynative.json (modules.app):

  • @sentry/react-native (with options)

Environment keys

KeyRequiredServer-onlyExampleDocs
EXPO_PUBLIC_SENTRY_DSNyesnohttps://examplePublicKey@o0.ingest.sentry.io/0dashboard

Keys go in .env (see .env.example). Required keys are checked by bun run doctor; Server-only keys have no EXPO_PUBLIC_ prefix, are read only by API routes and never reach the bundle.

Privacy

Play Data safety draft: collects Crash logs, Diagnostics (performance), Device info; shared with Sentry (processor). Source of truth: vendor disclosure.

Apple privacy manifest data types (composed into ios.privacyManifests by setup):

TypeLinked to userTrackingPurposes
CrashDatanonoAppFunctionality
PerformanceDatanonoAppFunctionality
OtherDiagnosticDatanonoAppFunctionality

Providers

Rendered in src/providers.tsx (lower order = outermost):

OrderProviderFrom
40CrashProvider@/lib/crash

Doctor checks

  • env: EXPO_PUBLIC_SENTRY_DSN

After setup

  1. Sentry: create a React Native project at https://sentry.io, copy its DSN (Settings > Projects > <project> > Client Keys) into EXPO_PUBLIC_SENTRY_DSN.
  2. Sentry: for source maps set EAS secrets SENTRY_ORG, SENTRY_PROJECT and SENTRY_AUTH_TOKEN (https://sentry.io/settings/auth-tokens/, scopes project:releases + org:read); the config plugin uploads on native builds, npx sentry-expo-upload-sourcemaps dist after eas update.
  3. Sentry: call crash.capture(new Error("test")) from any screen (or Examples > Crash reporting with --with-examples) → the issue appears under Issues within a minute.

Files

5 files copied to the project root
  • src/app/examples/crash.tsx
  • src/lib/__tests__/crash-boundary.test.tsx
  • src/lib/__tests__/crash-sentry.test.ts
  • src/lib/crash.ts
  • src/screens/examples/crash-example-screen.tsx

On this page

Get ReadyNative