Report crashes and errors from React Native and Expo apps to Flaggr's Issues page, with the flag variants each crash happened under and each release's crash-free sessions
Last updated October 18, 2026
React Native and Expo
@flaggr/react-native reports your app's crashes and errors to Flaggr. They show up on the console's Issues page as mobile issues. Each issue shows:
- the flag variants its events carried,
- the releases, app versions and devices it appears on,
- every event's stack, breadcrumbs, tags and context.
The SDK also reports each app launch as a session, so the Issues page's Releases view shows each release's crash-free rate.
It's plain TypeScript with no native module, so it works in Expo Go, development builds and bare React Native apps alike.
Install
npm install @flaggr/react-native @react-native-async-storage/async-storageCreate a Site for the app in Flaggr (Settings → Sites) and use its id:
import AsyncStorage from "@react-native-async-storage/async-storage";
import * as Application from "expo-application"; // optional, for Expo apps
import { createFlaggr } from "@flaggr/sdk";
import { flaggrReactNative } from "@flaggr/react-native";
export const errors = flaggrReactNative({
siteId: "site_…",
release: `com.acme.shop@${Application.nativeApplicationVersion}+${Application.nativeBuildVersion}`,
dist: Application.nativeBuildVersion ?? undefined,
environment: __DEV__ ? "development" : "production",
storage: AsyncStorage,
device: { appVersion: Application.nativeApplicationVersion ?? undefined, buildNumber: Application.nativeBuildVersion ?? undefined },
});
errors.install();
// With @flaggr/sdk, the same object is a plugin: crashes carry the variants the app evaluated.
export const flaggr = createFlaggr({ serviceId: "mobile-app", apiKey: "fgr_…", plugins: [errors] });Call install() as early as you can, before your app's root component renders.
What it catches
| Crashes | JavaScript errors that reach React Native's global handler (ErrorUtils). A fatal one is reported as a crash, level fatal |
| Errors | Non-fatal errors that reach the global handler, unhandled promise rejections (through Hermes, or the promise library on JavaScriptCore), and whatever you pass to captureException / captureMessage |
| Breadcrumbs | Flag evaluations, console.warn / console.error, fetch requests (without query strings), app state changes, and the screens you report with errors.trackScreen(name). Call it from React Navigation's onStateChange |
Native crashes aren't caught. These are crashes in Objective-C, Swift, Java, Kotlin or C++ code, your own or a library's. Reporting them needs a native crash reporter.
Crashes and storage
A fatal JavaScript error ends the app. The SDK saves the report with storage (AsyncStorage) before React Native's own handler runs, and waits up to 2 seconds to send it. If the report doesn't arrive, the next launch sends it. Without storage, a crash that can't be sent in time is lost.
storage also keeps a random install id. Issues count users by your setUser(id), and by the install id when you don't set one.
Readable stacks
Release builds report minified stacks: Hermes gives address at index.android.bundle:1:482113. Upload the bundle's source map for each release with the Flaggr CLI. The issue page then shows each frame as the original file, line and function:
flaggr sourcemaps upload android/app/build/generated/sourcemaps/react/release/index.android.bundle.map \
--release "com.acme.shop@2.1.0+45" --project <project-id>The file name is how stack frames find the map: index.android.bundle on Android, main.jsbundle on iOS. For Hermes, upload the composed map that the release build writes, so frames map from bytecode offsets to your source.
Flaggr groups mobile errors by the error's type, its message (with numbers, ids and quoted values taken out), and the innermost function name that survives minification. Positions change with every build, so grouping by them would split an issue. Use captureException(error, { fingerprint }) to group errors yourself.
Options
| Option | Default | |
|---|---|---|
siteId | (required) | The app's Site id |
apiKey | The project's SDK key, to file reports under the project's own id | |
release, dist, environment | Shown on issues; release drives the crash-free rate per release | |
storage | AsyncStorage, or any store with getItem, setItem and removeItem | |
device | from Platform | { model, osVersion, appVersion, buildNumber }, for values from expo-device / expo-application |
sessions | true | Report launches for crash-free rates. A return to the foreground after sessionTimeoutMs (30 s) starts a new session |
sampleRate | 1 | The share of non-fatal errors sent; crashes are always sent |
ignoreErrors, beforeSend | Drop errors by message, or change or drop any report | |
breadcrumbs | true | console and fetch breadcrumbs |
appUrl | https://flaggr.dev |
At most 30 errors a minute are sent, and an error object is reported once.
Privacy
The app's Site id is public, like everything in an app bundle. Reports send:
- the error, its stack and breadcrumbs,
- the device model, OS, app version and build,
- an install id, plus the user id and tags you set.
Flaggr removes tokens, email addresses and URL credentials from messages, stacks, breadcrumbs, tags and context before storing them. Add beforeSend to drop anything else.
Over the API, apps post to POST /api/ingest/mobile-errors with { siteId, events, sessions }. Each batch is limited to 50 events and 10 sessions, and each Site to 120 batches a minute.