Error Monitoring & Crash Reporting — Sentry React Native SDK
Minimum SDK:
@sentry/react-native≥ 6.0.0 (≥ 8.0.0 recommended) Native SDKs:sentry-cocoa(iOS/tvOS/macOS) ·sentry-android(Java + NDK) React Native: 0.71+ required for Fabric renderer support
React Native is unique: errors can originate from three different layers — the JavaScript runtime, native iOS (ObjC/Swift, Mach exceptions), or native Android (Java, JNI/C++ via NDK). The Sentry RN SDK bridges all three.
Table of Contents
- Core Capture APIs
- Native Crash Handling — iOS & Android
- ANR / App Hang Detection
- Unhandled Promise Rejections
- Sentry.wrap(App) — Top-Level Error Boundary
- ErrorBoundary Component
- Scope Management
- Context Enrichment — Tags, User, Extra, Contexts
- Breadcrumbs — Automatic & Manual
- beforeSend / beforeSendTransaction Hooks
- Fingerprinting & Grouping
- Event Processors
- Attachments — Screenshots & View Hierarchy
- Redux Integration
- Device & App Context
- Release Health & Sessions
- Offline Event Caching
- Default Integrations
- Full init() Options Reference
- Quick Reference Cheatsheet
- Troubleshooting
1. Core Capture APIs
Three fundamental data concepts:
- Event — a single submission to Sentry (exception, message, or raw event)
- Issue — a group of similar events clustered by Sentry
- Capturing — the act of reporting an event
Sentry.captureException(error, context?)
Captures any thrown Error (or non-Error value) and sends it to Sentry.
import * as Sentry from "@sentry/react-native";
// Basic usage
try {
aFunctionThatMightFail();
} catch (err) {
Sentry.captureException(err);
}
// With inline context (plain object)
Sentry.captureException(new Error("something went wrong"), {
tags: { section: "checkout" },
user: { email: "user@example.com" },
extra: { orderId: "abc-123" },
level: "warning",
fingerprint: ["{{ default }}", "checkout-error"],
});
// With a scope callback — clones scope for this capture only
Sentry.captureException(new Error("something went wrong"), (scope) => {
scope.setTag("section", "articles");
scope.setLevel("warning");
return scope;
});
// New Scope instance — merges with global scope
const scope = new Sentry.Scope();
scope.setTag("section", "articles");
Sentry.captureException(new Error("something went wrong"), scope);
// Isolate entirely — return the scope from a function to ignore global attrs
Sentry.captureException(new Error("clean slate"), () => scope);Sentry.captureMessage(message, level?)
Sends a textual message. Useful for non-exception events or informational milestones.
// Default level is "info"
Sentry.captureMessage("Something noteworthy happened");
// Explicit severity level
// "fatal" | "error" | "warning" | "log" | "info" | "debug"
Sentry.captureMessage("Payment declined", "warning");
Sentry.captureMessage("Critical system failure", "fatal");
Sentry.captureMessage("Debug checkpoint reached", "debug");Sentry.captureEvent(event)
Low-level method to send a fully constructed Sentry event object. Used for advanced cases where you build the event manually.
Sentry.captureEvent({
message: "Manual event",
level: "error",
tags: { custom_tag: "value" },
extra: { arbitrary_data: true },
fingerprint: ["my-custom-fingerprint"],
timestamp: Date.now() / 1000,
});Error Levels
| Level | Use Case |
|---|---|
fatal |
App crash, total loss of functionality |
error |
Feature broken, user action failed |
warning |
Degraded state, non-critical failure |
info |
Informational, noteworthy events |
log |
Low-priority operational logs |
debug |
Development diagnostics |
2. Native Crash Handling — iOS & Android
The React Native SDK delegates to two native SDKs for platform-level crash capture:
- iOS/tvOS/macOS —
sentry-cocoa - Android —
sentry-android(Java/Kotlin + NDK for C/C++)
How Native Crash Capture Works
Native crashes (segfaults, SIGSEGV, unhandled C++ exceptions, OOM kills) are captured entirely at the OS level — not in JavaScript. The crash handler is registered during native SDK initialization. Crash reports are:
- Persisted to disk in binary envelope format at crash time
- Not sent at crash time — queued and sent on the next app launch
iOS: [crash] → written to disk by sentry-cocoa
→ [next launch] → sentry-cocoa reads and transmits
Android: [crash] → written to disk by sentry-android
→ [next app restart] → sentry-android reads and transmitsNative Configuration Options
Sentry.init({
dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
// Disable all native SDK functionality (JS layer only)
enableNative: false,
// Prevent native layer from capturing hard crashes
enableNativeCrashHandling: false,
// Manually initialize native SDKs yourself (advanced)
autoInitializeNativeSdk: false,
// Sync Android Java scope data to NDK layer (for C/C++ crash context)
enableNdkScopeSync: true,
// Android 12+: use ApplicationExitInfo for enhanced tombstone reports
enableTombstone: true,
// Attach all thread states to Android events (has a performance impact)
attachThreads: false,
// Called after native SDKs have finished initializing
onReady: () => {
console.log("Sentry native SDKs initialized");
},
});Offline Caching Behavior
| Platform | Offline Behavior |
|---|---|
| Android | Events cached on device; transmitted on app restart |
| iOS | Events cached on device; transmitted when the next event fires |
Linked Errors (Chained .cause)
The NativeLinkedErrors integration (enabled by default) reads the .cause property on errors recursively, linking the error chain up to 5 levels deep:
try {
await fetchMovieReviews(movie);
} catch (originalError) {
const wrapperError = new Error(`Failed to fetch reviews for: ${movie}`);
wrapperError.cause = originalError; // SDK reads this chain
Sentry.captureException(wrapperError);
}3. ANR / App Hang Detection
Android — Application Not Responding (ANR)
ANR detection is handled by the native sentry-android SDK. Android's OS flags an ANR when:
- An activity doesn't respond to user input within 5 seconds
- A broadcast receiver doesn't complete within 10 seconds
The SDK detects this via a watchdog thread monitoring the main thread. When the UI thread is blocked, an ANR event is created and sent to Sentry. ANR detection on Android is always enabled via the native SDK and is not configurable from JavaScript.
iOS / tvOS / macOS — App Hangs
On Apple platforms, sentry-cocoa monitors the main thread with a watchdog. Any block exceeding the configured threshold triggers an error event.
Sentry.init({
dsn: "___PUBLIC_DSN___",
// Disable app hang tracking (Apple platforms only)
enableAppHangTracking: false,
// Detection threshold in seconds (default: 2)
// Main thread must be blocked longer than this value to trigger
appHangTimeoutInterval: 1,
});Note:
enableAppHangTrackingandappHangTimeoutIntervalapply to iOS, tvOS, and macOS only.
iOS Watchdog Terminations & OOM
Sentry.init({
// Track out-of-memory kills and watchdog terminations on iOS (default: true)
enableWatchdogTerminationTracking: true,
});4. Unhandled Promise Rejections
The SDK automatically captures unhandled promise rejections via the built-in UnhandledRejection integration. Any promise that rejects without a .catch() or try/catch is captured as a Sentry error event with no configuration needed.
// This is automatically captured by Sentry:
async function doSomething() {
throw new Error("Unhandled rejection");
}
doSomething(); // No await, no .catch()
// To disable (if you handle these yourself elsewhere):
Sentry.init({
integrations: (integrations) =>
integrations.filter((i) => i.name !== "UnhandledRejection"),
});5. Sentry.wrap(App) — Top-Level Error Boundary
Sentry.wrap wraps your root component and should be used in every React Native app using Sentry.
// index.js / app entry point
import { AppRegistry } from "react-native";
import * as Sentry from "@sentry/react-native";
import App from "./src/App";
import { name as appName } from "./app.json";
Sentry.init({
dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
});
AppRegistry.registerComponent(appName, () => Sentry.wrap(App));What Sentry.wrap does:
| Capability | Description |
|---|---|
| React render error boundary | Catches errors thrown during component rendering |
| UI interaction tracking | Records touch events as ui.click breadcrumbs automatically |
| User Feedback Widget | Sentry.showFeedbackWidget() requires this wrapper |
| Session Replay buffering | Buffers pre-error session data for the feedback widget |
6. ErrorBoundary Component
Sentry.ErrorBoundary is a React error boundary that catches render-time errors, reports them to Sentry with full React component stack context, and renders a fallback UI.
Basic Usage
import * as Sentry from "@sentry/react-native";
function App() {
return (
<Sentry.ErrorBoundary fallback={<Text>An error has occurred</Text>}>
<Dashboard />
</Sentry.ErrorBoundary>
);
}Fallback as a Function
import * as Sentry from "@sentry/react-native";
function App() {
return (
<Sentry.ErrorBoundary
fallback={({ error, componentStack, resetError }) => (
<View style={styles.errorContainer}>
<Text style={styles.title}>Something went wrong</Text>
<Text style={styles.message}>{error.toString()}</Text>
<Text style={styles.stack}>{componentStack}</Text>
<Button title="Try again" onPress={resetError} />
</View>
)}
>
<MainContent />
</Sentry.ErrorBoundary>
);
}The fallback function receives:
error— the thrown error objectcomponentStack— React's component stack trace stringresetError— function to clear error state and re-render children
Higher-Order Component (HOC) Pattern
import * as Sentry from "@sentry/react-native";
const SafeDashboard = Sentry.withErrorBoundary(Dashboard, {
fallback: <View><Text>Dashboard unavailable</Text></View>,
});Multiple Boundaries with Contextual Tags
function App() {
return (
<View>
<Sentry.ErrorBoundary
fallback={<SidebarFallback />}
beforeCapture={(scope) => scope.setTag("section", "sidebar")}
>
<Sidebar />
</Sentry.ErrorBoundary>
<Sentry.ErrorBoundary
fallback={<ContentFallback />}
beforeCapture={(scope) => scope.setTag("section", "content")}
>
<MainContent />
</Sentry.ErrorBoundary>
</View>
);
}Nesting error boundaries allows granular isolation: an error in Sidebar won't crash MainContent, and each boundary tags its errors with a section for easy filtering in Sentry.
Show User Feedback Dialog on Error
<Sentry.ErrorBoundary
showDialog // auto-opens user feedback dialog when error is caught
fallback={<ErrorScreen />}
>
<App />
</Sentry.ErrorBoundary>Full Props Reference
| Prop | Type | Description |
|---|---|---|
fallback |
ReactNode | ({ error, componentStack, resetError }) => ReactNode |
UI rendered when an error is caught |
showDialog |
boolean |
Open User Feedback widget on error |
dialogOptions |
object |
Options passed to the feedback dialog |
onError |
(error, componentStack, eventId) => void |
Called when an error is caught; useful for state propagation |
beforeCapture |
(scope, error, componentStack) => void |
Called before sending to Sentry; add tags/context here |
onMount |
() => void |
Called on componentDidMount |
onUnmount |
() => void |
Called on componentWillUnmount |
Manual Error Boundary (Class Component)
import React from "react";
import * as Sentry from "@sentry/react-native";
class CustomErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
Sentry.captureException(error, {
extra: { componentStack: info.componentStack },
});
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? null;
}
return this.props.children;
}
}Important: Custom error boundaries must be class components — this is a React requirement, not a Sentry limitation.
7. Scope Management
Scopes hold contextual data (tags, user, breadcrumbs, contexts) that is merged into captured events. There are three scope layers with different lifetimes.
Three Scope Types
Global Scope
Applied to every event regardless of origin. Used for low-level environmental data.
const globalScope = Sentry.getGlobalScope();
globalScope.setTag("app_type", "mobile");
globalScope.setContext("runtime", { name: "Hermes", version: "0.11.0" });Isolation Scope
Separates events from each other (per-session in mobile). All Sentry.setXXX() convenience methods write here.
// These are equivalent:
Sentry.setTag("my-tag", "my value");
Sentry.getIsolationScope().setTag("my-tag", "my value");
// Set user for the entire session:
Sentry.setUser({ id: "42", email: "user@example.com" });Current Scope
The locally active scope. Best accessed via withScope().
Scope Data Precedence
Scopes merge in order: global → isolation → current. A key on the current scope overrides the same key on outer scopes.
Sentry.getGlobalScope().setExtras({ shared: "global", global: "data" });
Sentry.getIsolationScope().setExtras({ shared: "isolation", isolation: "data" });
Sentry.getCurrentScope().setExtras({ shared: "current", current: "data" });
// Resulting event extras: { shared: "current", global: "data", isolation: "data", current: "data" }withScope() — Temporary Isolated Scopes
Creates a cloned scope valid only inside the callback. Changes do not affect the outer scope.
// Error 1 gets the tag; Error 2 does NOT
Sentry.withScope((scope) => {
scope.setTag("my-tag", "my value");
scope.setLevel("warning");
Sentry.captureException(new Error("my error")); // tagged
});
Sentry.captureException(new Error("my other error")); // NOT tagged
// Temporarily override user identity for one capture
Sentry.withScope((scope) => {
scope.setUser({ id: "service-account" });
Sentry.captureException(backgroundJobError);
// original user identity restored after this block
});Convenience Methods (All Write to Isolation Scope)
Sentry.setTag(key, value)
Sentry.setTags({ key: value })
Sentry.setUser({ id, email, username })
Sentry.setContext(name, object)
Sentry.setExtra(key, value)
Sentry.setExtras({ key: value })
Sentry.addBreadcrumb(breadcrumb)8. Context Enrichment — Tags, User, Extra, Contexts
Tags — Indexed & Searchable
Tags are key/value string pairs indexed in Sentry, enabling full-text search, filter sidebars, and distribution maps in the UI.
Sentry.setTag("page_locale", "de-at");
Sentry.setTag("app_version", "3.2.1");
Sentry.setTag("user_plan", "enterprise");Tag constraints:
| Property | Constraint |
|---|---|
| Key max length | 32 characters |
| Key allowed characters | a-zA-Z, 0-9, _, ., :, - |
| Value max length | 200 characters |
| Value forbidden | Newline \n characters |
User Identity
// Set on login
Sentry.setUser({
id: "42",
email: "john.doe@example.com",
username: "johndoe",
ip_address: "{{auto}}", // Sentry resolves this automatically
// Any additional key-value pairs
plan: "enterprise",
role: "admin",
});
// Clear on logout
Sentry.setUser(null);Custom Structured Contexts
Structured contexts attach arbitrary nested objects to events. They appear on the issue detail page but are not searchable (use tags for searchable data).
Sentry.setContext("character", {
name: "Mighty Fighter",
age: 19,
attack_type: "melee",
});
Sentry.setContext("order", {
id: "ORD-9821",
total: 129.99,
items: ["item-1", "item-2"],
shipping: { method: "express", address: "123 Main St" },
});Notes:
- The key
"type"is reserved by Sentry — do not use it- Context nesting is normalized to 3 levels by default (configurable via
normalizeDepth)- Avoid sending entire app state blobs; exceeding max payload size triggers HTTP
413
Extra Data (Deprecated)
// Deprecated — use setContext() instead
Sentry.setExtra("server_name", "web-01");
Sentry.setExtras({ key1: "value1", key2: "value2" });Inline Context on Capture Calls
Sentry.captureException(new Error("something went wrong"), {
tags: { section: "articles" },
user: { id: "42", email: "user@example.com" },
extra: { requestId: "abc-123" },
contexts: { order: { id: "ORD-9821" } },
level: "warning",
fingerprint: ["{{ default }}", "order-error"],
});Clearing Context
// Clear all scope data
Sentry.getCurrentScope().clear();
// Reset user
Sentry.setUser(null);
// Remove a specific tag
Sentry.setTag("key", undefined);9. Breadcrumbs — Automatic & Manual
Breadcrumbs form a timeline of events leading up to an error. They buffer until the next event is captured — they do not create Sentry issues on their own.
Manual Breadcrumbs
import * as Sentry from "@sentry/react-native";
// Navigation event
Sentry.addBreadcrumb({
category: "navigation",
message: "Navigated to screen",
level: "info",
data: {
from: "HomeScreen",
to: "ProfileScreen",
params: { userId: "42" },
},
});
// Authentication event
Sentry.addBreadcrumb({
category: "auth",
message: "User logged in: " + user.email,
level: "info",
});
// API failure before throwing
Sentry.addBreadcrumb({
category: "api",
message: "Checkout API call failed",
level: "error",
data: {
url: "/api/checkout",
status: 500,
method: "POST",
},
});Breadcrumb properties:
| Property | Description |
|---|---|
type |
"default", "http", "navigation", "user" |
category |
Dot-separated string (e.g., "ui.click", "http", "auth") |
message |
Human-readable description |
level |
"fatal", "critical", "error", "warning", "log", "info", "debug" |
timestamp |
Unix timestamp (auto-set if omitted) |
data |
Arbitrary { key: value } metadata |
Warning: Unknown keys beyond those above are silently dropped during processing.
Automatic Breadcrumbs
| Source | Category | How |
|---|---|---|
| Touch interactions | ui.click |
Via Sentry.wrap on root component |
| HTTP requests | http |
Fetch/XHR patching (default) |
| Console output | console |
console.log/warn/error patching (default) |
| Navigation | navigation |
Via navigation integrations |
| Redux actions | redux.action |
Via Sentry.createReduxEnhancer |
| Native lifecycle | various | From native SDKs (connectivity changes, lifecycle events) |
beforeBreadcrumb Hook
Sentry.init({
beforeBreadcrumb(breadcrumb, hint) {
// Drop all UI click breadcrumbs
if (breadcrumb.category === "ui.click") {
return null;
}
// Scrub auth tokens from HTTP breadcrumbs
if (breadcrumb.category === "http" && breadcrumb.data?.url) {
breadcrumb.data.url = breadcrumb.data.url.replace(
/token=[^&]*/,
"token=REDACTED"
);
}
// Add extra metadata to console breadcrumbs
if (breadcrumb.category === "console") {
breadcrumb.data = { ...breadcrumb.data, deviceTime: Date.now() };
}
return breadcrumb; // return null to drop
},
});Breadcrumb Capacity
Sentry.init({
// Default is 100; oldest breadcrumbs are discarded when full
maxBreadcrumbs: 50,
});10. beforeSend / beforeSendTransaction Hooks
These hooks fire immediately before an event is transmitted, giving you a final chance to modify or suppress it.
Important:
beforeSendonly runs on JavaScript-layer events. It does not affect native Android/iOS crash events captured by the native SDKs.
beforeSend — Error Events
Sentry.init({
beforeSend(event, hint) {
// hint.originalException — the original thrown Error object
// hint.syntheticException — auto-generated when non-Error is thrown
// hint.event_id — the generated event ID
// Drop events matching a pattern
if (event.exception?.values?.[0]?.value?.includes("ResizeObserver")) {
return null;
}
// Scrub PII before sending
if (event.user) {
delete event.user.email;
delete event.user.ip_address;
}
// Set fingerprint based on error message
const error = hint.originalException as Error;
if (error?.message?.match(/database unavailable/i)) {
event.fingerprint = ["database-unavailable"];
}
// Attach extra data
event.extra = {
...event.extra,
build_number: "42",
};
return event; // return null to drop, return event to send
},
});beforeSendTransaction — Performance Transactions
Sentry.init({
beforeSendTransaction(event) {
// Drop health check transactions
if (event.transaction === "/health") return null;
// Normalize internal transaction names
if (event.transaction?.startsWith("/internal/")) {
event.transaction = "/internal/*";
}
return event;
},
});ignoreErrors / ignoreTransactions
Pre-filter before beforeSend even runs — more efficient for known noise patterns:
Sentry.init({
ignoreErrors: [
"ResizeObserver loop limit exceeded",
"Non-Error exception captured",
/^Script error\.?$/,
],
ignoreTransactions: [
"/healthcheck",
/^\/admin\/internal\//,
],
});11. Fingerprinting & Grouping
Fingerprinting controls how Sentry groups events into issues. By default, Sentry groups by stack trace. You can override this to merge or split issues.
SDK-Level Fingerprinting
// Static fingerprint — all matching events become one issue
Sentry.captureException(new Error("DB connection failed"), {
fingerprint: ["database-connection-error"],
});
// Dynamic — include URL and status for more granular groups
Sentry.captureException(networkErr, {
fingerprint: ["{{ default }}", networkErr.url, String(networkErr.status)],
});
// Dynamic via beforeSend
Sentry.init({
beforeSend(event, hint) {
const error = hint.originalException as Error;
if (error?.message?.match(/network request failed/i)) {
event.fingerprint = [
"network-error",
event.request?.url ?? "unknown-url",
];
}
return event;
},
});Fingerprint Variables
| Variable | Resolves to |
|---|---|
{{ default }} |
Sentry's default grouping hash |
{{ error.type }} |
Exception class name |
{{ error.value }} |
Exception message text |
{{ transaction }} |
Current transaction name |
{{ level }} |
Event severity level |
{{ message }} |
Captured message |
{{ stack.function }} |
Top stack frame function name |
{{ stack.module }} |
Top stack frame module |
Server-Side Fingerprint Rules (Project Settings)
# Group all DB errors together regardless of message
error.type:DatabaseUnavailable -> system-down
error.type:ConnectionError -> system-down
# Subdivide connection errors by transaction
error.value:"connection error: *" -> connection-error, {{ transaction }}
# Custom issue title
logger:my.package.* level:error -> error-logger, {{ logger }} title="Error from Logger {{ logger }}"Fingerprint Priority
- SDK-set
fingerprint(incaptureException,beforeSend, orcaptureEvent) - Server-side fingerprint rules (Sentry project settings)
- Sentry's default stack-trace-based grouping
12. Event Processors
Event processors run on every event before transmission. They differ from beforeSend in two key ways:
beforeSendalways runs last, after all event processors- Processors added to a scope only apply to events within that scope
Global Event Processor
import * as Sentry from "@sentry/react-native";
Sentry.addEventProcessor((event, hint) => {
// Enrich all events with app metadata
event.extra = {
...event.extra,
appBuildTime: BUILD_TIMESTAMP,
featureFlags: getActiveFeatureFlags(),
};
// Drop events from test environments
if (isTestEnvironment()) return null;
return event;
});Scoped Event Processor
Sentry.withScope((scope) => {
scope.addEventProcessor((event, hint) => {
// Only runs for events captured inside this withScope block
event.tags = { ...event.tags, flow: "checkout" };
return event;
});
Sentry.captureException(checkoutError); // ✅ processor fires
});
Sentry.captureException(otherError); // ❌ processor does NOT fireAsync Event Processors
Sentry.addEventProcessor(async (event, hint) => {
const deviceInfo = await getDeviceInfo();
event.contexts = { ...event.contexts, device: deviceInfo };
return event;
});Execution Order
[All addEventProcessor / scope.addEventProcessor functions]
↓ (in registration order)
[beforeSend / beforeSendTransaction]
↓ (always last)
[Sentry servers]13. Attachments — Screenshots & View Hierarchy
Automatic Screenshot on Error
Captures a PNG screenshot at the moment an error occurs. Attached to the event in Sentry's issue detail view.
Sentry.init({
// Available since @sentry/react-native v4.11.0
attachScreenshot: true,
});Screenshots appear under "Attachments" on the event detail page in Sentry.
PII consideration: Screenshots may capture sensitive data visible on screen (forms, personal information). Review before enabling in production.
View Hierarchy Capture
Captures a JSON representation of the native component hierarchy at crash time.
Sentry.init({
attachViewHierarchy: true,
});The view hierarchy appears in Sentry's "View Hierarchy" tab on the event.
Manual File Attachments
Sentry.captureException(err, {
attachments: [
{
filename: "config.json",
data: JSON.stringify(appConfig),
contentType: "application/json",
},
{
filename: "debug.log",
data: logFileContents, // string or Uint8Array
contentType: "text/plain",
},
{
filename: "screenshot.png",
data: base64PngData,
contentType: "image/png",
},
],
});Attachments via Scope
Sentry.withScope((scope) => {
scope.addAttachment({
filename: "state_snapshot.json",
data: JSON.stringify(store.getState()),
contentType: "application/json",
});
Sentry.captureException(error);
});Size limits: Attachments must not push the total event payload over Sentry's maximum. Oversized payloads return HTTP
413 Payload Too Large.
14. Redux Integration
The createReduxEnhancer captures Redux state snapshots and action history as breadcrumbs on error events.
Setup
import { createStore } from "redux";
import * as Sentry from "@sentry/react-native";
const store = createStore(
rootReducer,
Sentry.createReduxEnhancer({
// Transform action before recording — return null to skip
actionTransformer: (action) => {
if (action.type === "SENSITIVE_ACTION") return null;
if (action.type === "SET_PASSWORD") {
return { ...action, payload: "[REDACTED]" };
}
return action;
},
// Transform state snapshot — avoid sending large state trees
stateTransformer: (state) => ({
selectedTab: state.ui.selectedTab,
userPlan: state.user.plan,
cartItemCount: state.cart.items.length,
}),
})
);With Redux Toolkit
import { configureStore } from "@reduxjs/toolkit";
import * as Sentry from "@sentry/react-native";
const store = configureStore({
reducer: rootReducer,
enhancers: (getDefaultEnhancers) =>
getDefaultEnhancers().concat(
Sentry.createReduxEnhancer({
actionTransformer: (action) => {
// Drop auth-related actions from breadcrumbs
if (action.type.startsWith("auth/")) return null;
return action;
},
})
),
});Dispatched actions appear in Sentry as redux.action breadcrumbs. State at the time of an error is attached to the event under state.value.
15. Device & App Context
The SDK automatically attaches rich device context to every event — no configuration required.
Automatic Context (No Setup Needed)
| Context Section | Fields | Source |
|---|---|---|
| Device | Model, manufacturer, brand, screen resolution, orientation, free memory, battery level, charging state | Native SDK |
| OS | Name (iOS/Android), version, build number, kernel version |
Native SDK |
| App | App ID, version name, version code, build type | Native SDK |
| React Native | RN version, JS engine (Hermes/JSC), architecture | JS SDK |
These appear in Sentry under the "Device", "Operating System", and "App" sections of any event.
Overriding or Extending Device Context
Sentry.setContext("device", {
custom_hardware_id: "DEVICE-UUID-123",
});
Sentry.setContext("app", {
app_version: "3.2.1",
app_build: "421",
custom_build_flavor: "staging",
});Release, Distribution & Environment
Sentry.init({
// Used in Sentry for regression detection and release health
release: "com.myapp@3.2.1+421",
// Distinguishes builds within a release (e.g., Xcode build number)
dist: "421",
// Shown on every event for filtering
environment: "production", // "staging" | "development" | "production"
});16. Release Health & Sessions
Sentry tracks session-based metrics to surface crash-free rates and regressions across app versions.
How Sessions Work
A session begins when the app comes to the foreground and ends when it goes to background for longer than sessionTrackingIntervalMillis (default: 30 seconds). Each session maps to a release version, enabling Sentry to compute:
- Crash-free session rate — % of sessions without a fatal crash
- Crash-free user rate — % of users without a crash in a given release
Sentry.init({
release: "com.myapp@3.2.1+421",
autoSessionTracking: true, // default: true
sessionTrackingIntervalMillis: 30000, // default: 30s background threshold
});Sessions are sent automatically. No additional API calls are required.
17. Offline Event Caching
The SDK caches events locally when the device has no network connectivity. Events are transmitted automatically when connectivity is restored.
Sentry.init({
// Maximum number of envelopes to cache on disk (default: 30)
maxCacheItems: 30,
});| Platform | Cache Location | Transmission Trigger |
|---|---|---|
| Android | Internal app storage | App restart |
| iOS | App sandbox Library/Caches/ |
Next event fires |
Offline caching works for both JS-layer events and native crash reports.
18. Default Integrations
The following integrations are enabled automatically:
| Integration | Purpose |
|---|---|
| InboundFilters | Drops events matching ignoreErrors, denyUrls, allowUrls. Default-ignores "Script error" |
| FunctionToString | Preserves original function names even when SDK wraps handlers |
| Breadcrumbs | Patches console, fetch, XHR to auto-capture breadcrumbs |
| NativeLinkedErrors | Reads .cause chains up to 5 levels deep |
| HttpContext | Attaches URL, user-agent, referrer to events |
| Dedupe | Prevents duplicate consecutive events from being reported |
| UnhandledRejection | Auto-captures unhandled promise rejections |
Customizing Default Integrations
// Disable all defaults (rarely needed)
Sentry.init({ defaultIntegrations: false });
// Disable console breadcrumbs only
Sentry.init({
integrations: [
Sentry.breadcrumbsIntegration({
console: false, // disable console breadcrumbs
fetch: true,
xhr: true,
sentry: true,
// Note: `dom` and `history` are web-only — not applicable in React Native
}),
],
});
// Remove a specific integration
Sentry.init({
integrations: (integrations) =>
integrations.filter((i) => i.name !== "Breadcrumbs"),
});Opt-In Integrations
Sentry.init({
integrations: [
// Capture failed HTTP requests (non-2xx) as Sentry errors (v5.3.0+)
Sentry.httpClientIntegration({
failedRequestStatusCodes: [[400, 599]],
failedRequestTargets: ["https://api.myapp.com"],
}),
// Rewrite stack frame file paths (useful for custom source map layouts)
Sentry.rewriteFramesIntegration({ root: "/" }),
],
// Shorthand for httpClientIntegration with default settings:
enableCaptureFailedRequests: true,
});19. Full init() Options Reference
import * as Sentry from "@sentry/react-native";
Sentry.init({
// ── Core ──────────────────────────────────────────────────────────
dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
enabled: true, // false disables all SDK transmission
debug: false, // log SDK internals to console
release: "com.myapp@3.2.1+421",
dist: "421", // distinguishes builds within a release
environment: "production",
sampleRate: 1.0, // 0.0–1.0; fraction of error events to send
// ── Filtering ─────────────────────────────────────────────────────
ignoreErrors: ["Script error", /^Non-Error/],
ignoreTransactions: ["/healthcheck"],
// denyUrls / allowUrls match stack frame URLs — primarily useful for web;
// in React Native these can filter native frames but are rarely needed.
// denyUrls: ["chrome-extension://", /extensions\//i],
// allowUrls: ["https://myapp.com"],
maxBreadcrumbs: 100,
maxValueLength: 250, // max length of string values in events
// ── Normalization ─────────────────────────────────────────────────
normalizeDepth: 3, // depth to normalize context objects
normalizeMaxBreadth: 1000, // max number of object properties
// ── Hooks ─────────────────────────────────────────────────────────
beforeSend(event, hint) {
// JS-layer events only. Return null to drop.
return event;
},
beforeSendTransaction(event) {
return event;
},
beforeBreadcrumb(breadcrumb, hint) {
return breadcrumb; // return null to drop
},
// ── Attachments ───────────────────────────────────────────────────
attachStacktrace: true, // stack traces on captureMessage calls
attachScreenshot: false, // auto-screenshot on error (v4.11.0+)
attachViewHierarchy: false, // native view hierarchy JSON on error
sendDefaultPii: false, // allow integrations to send PII
// ── Transport ─────────────────────────────────────────────────────
maxCacheItems: 30, // max envelopes cached offline
shutdownTimeout: 2000, // ms to wait for queue drain on shutdown
// ── Sessions ──────────────────────────────────────────────────────
autoSessionTracking: true,
sessionTrackingIntervalMillis: 30000,
// ── Performance / Tracing ─────────────────────────────────────────
tracesSampleRate: 0.2,
tracesSampler: ({ name, attributes, parentSampled }) => {
if (name.includes("healthcheck")) return 0;
if (typeof parentSampled === "boolean") return parentSampled;
return 0.2;
},
tracePropagationTargets: ["localhost", /^https:\/\/api\.myapp\.com/],
enableAutoPerformanceTracing: true,
// ── Native / Hybrid ───────────────────────────────────────────────
enableNative: true,
enableNativeCrashHandling: true,
autoInitializeNativeSdk: true,
enableNdkScopeSync: true, // sync Java scope to NDK (Android)
enableTombstone: true, // Android 12+ ApplicationExitInfo (default: false)
attachThreads: false, // all threads on Android events
enableNativeNagger: true, // warn if native init fails
enableWatchdogTerminationTracking: true, // iOS OOM tracking
// ── ANR / App Hang ────────────────────────────────────────────────
enableAppHangTracking: true, // Apple platforms only
appHangTimeoutInterval: 2, // Apple platforms only, seconds
// ── HTTP Client ───────────────────────────────────────────────────
enableCaptureFailedRequests: false, // auto-capture HTTP errors (v5.3.0+)
// ── Callbacks ─────────────────────────────────────────────────────
onReady: () => console.log("Sentry native SDKs initialized"),
// ── Integrations ──────────────────────────────────────────────────
integrations: [
Sentry.feedbackIntegration({
styles: { submitButton: { backgroundColor: "#6a1b9a" } },
}),
Sentry.httpClientIntegration(),
],
defaultIntegrations: true, // false disables all built-in integrations
});20. Quick Reference Cheatsheet
import * as Sentry from "@sentry/react-native";
// ── Init & Wrap ────────────────────────────────────────────────────
Sentry.init({ dsn: "...", release: "...", environment: "production" });
export default Sentry.wrap(App); // required for touch breadcrumbs + feedback widget
// ── Capture ───────────────────────────────────────────────────────
Sentry.captureException(new Error("oh no"));
Sentry.captureMessage("Something happened", "warning");
Sentry.captureEvent({ message: "raw event", level: "info" });
// ── Identity & Context ────────────────────────────────────────────
Sentry.setUser({ id: "42", email: "user@example.com" });
Sentry.setTag("version", "3.2.1");
Sentry.setContext("order", { id: "ORD-99", total: 59.99 });
// ── Scopes ────────────────────────────────────────────────────────
Sentry.withScope((scope) => {
scope.setTag("temp", "value");
Sentry.captureException(err);
});
Sentry.getGlobalScope().setTag("app", "mobile");
Sentry.getCurrentScope().clear();
// ── Breadcrumbs ───────────────────────────────────────────────────
Sentry.addBreadcrumb({ category: "auth", message: "Login", level: "info" });
// ── Error Boundaries ──────────────────────────────────────────────
<Sentry.ErrorBoundary
fallback={({ error, resetError }) => (
<View><Text>{error.toString()}</Text><Button onPress={resetError} title="Retry" /></View>
)}
beforeCapture={(scope) => scope.setTag("section", "main")}
>
<App />
</Sentry.ErrorBoundary>
// ── Event Processor ───────────────────────────────────────────────
Sentry.addEventProcessor((event) => { event.extra = { foo: "bar" }; return event; });21. Troubleshooting
| Issue | Solution |
|---|---|
| Events not appearing in Sentry | Check DSN is correct; set debug: true to see SDK logs; verify enabled: true; check for beforeSend returning null |
| Native crashes not reported | Ensure enableNative: true and enableNativeCrashHandling: true; check that native SDKs initialized (look for onReady callback firing) |
| ANR/hang events not appearing | Android ANR is always on; for iOS, verify enableAppHangTracking: true and try lowering appHangTimeoutInterval |
Sentry.wrap not working |
Confirm it wraps the root component registered with AppRegistry (not an inner component) |
showFeedbackWidget() crashes |
App must be wrapped with Sentry.wrap(App); ensure Fabric (new arch) requires RN ≥ 0.71 |
| Screenshots are blank | Screenshot capture may be blocked on certain Android versions; ensure attachScreenshot: true |
beforeSend not filtering native crashes |
beforeSend only filters JS-layer events; native crashes bypass it — use enableNativeCrashHandling: false to disable native crash capture entirely |
| Duplicate events appearing | Check for multiple Sentry.init() calls; Dedupe integration handles sequential duplicates but not concurrent ones |
| Too many breadcrumbs / events | Reduce maxBreadcrumbs; use beforeBreadcrumb to filter; use sampleRate to reduce event volume |
| HTTP errors not captured | Add enableCaptureFailedRequests: true (v5.3.0+) or configure httpClientIntegration() |
| Missing stack frames (minified) | Upload source maps via Sentry CLI or the Metro plugin; check dist and release match the build |
setContext data not appearing |
Verify key "type" is not used (reserved); check normalizeDepth isn't truncating nested data |
| Event payload rejected with 413 | Attachment or context too large; use stateTransformer in Redux enhancer; limit attachment sizes |
| Offline events not sent | Events are sent on next app launch (Android) or next event fire (iOS); check maxCacheItems isn't set too low |