Skip to content

2026-02-16

Sentry Integration with React Native Expo: A Practical Quick Guide

Step-by-step guide to integrating Sentry error monitoring into a React Native Expo app. Covers SDK initialization, Expo Router instrumentation, session replay, source map uploads for EAS Build and EAS Update, and common pitfalls to avoid.

This guide walks through integrating Sentry into a React Native Expo application, from installation to production-grade source map uploads. It covers the critical configuration details, Expo Go limitations, session replay setup, and the gotchas that tend to waste hours if you encounter them unprepared.

Prerequisites

Before starting, you need:

  • An Expo project using SDK 50 or later (SDK 52+ recommended)
  • A Sentry account (free tier provides 5,000 errors/month)
  • A Sentry React Native project created in the dashboard
  • Three identifiers from Sentry: Organization slug, Project name, and DSN
  • An Organization Auth Token generated from Sentry settings

Step 1: Installation

The Sentry wizard automates most of the setup. Run it from your project root:

npx @sentry/wizard@latest -i reactNative

The wizard handles Metro config, Expo plugin config, and basic SDK initialization. If you prefer manual setup or the wizard does not work in your environment, install the package directly:

npx expo install @sentry/react-native

Manual setup requires configuring three files yourself, which the following steps cover in detail.

Step 2: Metro Configuration

Sentry needs to hook into the Metro bundler for source map generation. Replace your metro.config.js with:

// metro.config.js
const { getSentryExpoConfig } = require("@sentry/react-native/metro");

const config = getSentryExpoConfig(__dirname);

module.exports = config;

If you already have a custom Metro config, wrap your existing config with getSentryExpoConfig instead of replacing it entirely.

Step 3: Expo Plugin Configuration

Add the Sentry plugin to your app.json (or app.config.js):

{
  "expo": {
    "plugins": [
      [
        "@sentry/react-native/expo",
        {
          "url": "https://sentry.io/",
          "project": "my-expo-app",
          "organization": "my-org"
        }
      ]
    ]
  }
}

Warning: Never put authToken in app.json or app.config.js. This config gets embedded in the app bundle and is accessible at runtime. Set the auth token as an environment variable or EAS secret instead. This was a documented security issue in the Expo SDK 50 migration docs.

Step 4: SDK Initialization with Expo Router

The main initialization goes in your root layout file. This example includes performance monitoring with Expo Router navigation tracking, environment configuration, and session replay:

// app/_layout.tsx
import { Stack, useNavigationContainerRef } from "expo-router";
import * as Sentry from "@sentry/react-native";
import { isRunningInExpoGo } from "expo";
import React from "react";

const navigationIntegration = Sentry.reactNavigationIntegration({
  enableTimeToInitialDisplay: !isRunningInExpoGo(),
});

Sentry.init({
  dsn: "https://[email protected]/0",

  // Environment configuration
  environment: __DEV__ ? "development" : "production",

  // Performance monitoring
  tracesSampleRate: __DEV__ ? 1.0 : 0.2,
  enableNativeFramesTracking: !isRunningInExpoGo(),

  // Session replay
  replaysOnErrorSampleRate: 1.0,
  replaysSessionSampleRate: __DEV__ ? 1.0 : 0.1,

  // Integrations
  integrations: [
    navigationIntegration,
    Sentry.mobileReplayIntegration(),
  ],
});

function RootLayout() {
  const ref = useNavigationContainerRef();

  React.useEffect(() => {
    if (ref?.current) {
      navigationIntegration.registerNavigationContainer(ref);
    }
  }, [ref]);

  return <Stack />;
}

export default Sentry.wrap(RootLayout);

A few things worth noting in this configuration:

  • tracesSampleRate: 0.2 in production captures 20% of transactions. Setting this to 1.0 in production will burn through your quota quickly and can impact app performance.
  • replaysOnErrorSampleRate: 1.0 captures session replays for every error, which is the most valuable use case. General session replays at 0.1 provide a sample without excessive data.
  • isRunningInExpoGo() guards prevent enabling native-only features in Expo Go, where they would silently fail or crash.

Step 5: Understanding Expo Go vs Development Builds

This is one of the most common sources of confusion. Expo Go does not include Sentry’s native modules, which limits functionality significantly.

Development Build / EAS Build

JS Error Capture - Works

Message Capture - Works

Breadcrumbs - Works

User Context - Works

Native Crashes - Works

Frame Tracking - Works

Session Replay - Works

Native Profiling - Works

Expo Go

JS Error Capture - Works

Message Capture - Works

Breadcrumbs - Works

User Context - Works

Native Crashes - Not Available

Frame Tracking - Not Available

Session Replay - Not Available

Native Profiling - Not Available

The isRunningInExpoGo() guard prevents enabling native-only features when running in Expo Go:

Sentry.init({
  // ...
  enableNativeFramesTracking: !isRunningInExpoGo(),
  profilesSampleRate: isRunningInExpoGo() ? 0 : 1.0,
});

For full Sentry functionality, always validate with a development build or EAS Build before shipping.

Step 6: Error Boundaries

Sentry provides an ErrorBoundary component that catches React rendering errors and reports them automatically:

import * as Sentry from "@sentry/react-native";
import { View, Text, TouchableOpacity } from "react-native";

function ErrorFallback({
  error,
  resetError,
}: {
  error: Error;
  resetError: () => void;
}) {
  return (
    <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
      <Text style={{ fontSize: 18, fontWeight: "bold" }}>
        Something went wrong
      </Text>
      <Text style={{ color: "#666", marginTop: 8 }}>
        {error.message}
      </Text>
      <TouchableOpacity
        onPress={resetError}
        style={{
          marginTop: 16,
          padding: 12,
          backgroundColor: "#362d59",
          borderRadius: 8,
        }}
      >
        <Text style={{ color: "#fff" }}>Try Again</Text>
      </TouchableOpacity>
    </View>
  );
}

export function ScreenWithErrorBoundary() {
  return (
    <Sentry.ErrorBoundary
      fallback={({ error, resetError }) => (
        <ErrorFallback error={error} resetError={resetError} />
      )}
      beforeCapture={(scope) => {
        scope.setTag("component", "MainScreen");
      }}
    >
      <MainScreenContent />
    </Sentry.ErrorBoundary>
  );
}

The beforeCapture callback lets you add context tags, making it easier to filter and group errors in the Sentry dashboard.

Note: In React development mode, React rethrows errors caught by error boundaries to the global error handler, which can cause duplicate reports. Test error boundary behavior in production builds.

Step 7: Source Maps — EAS Build and EAS Update

Getting readable stack traces in production requires properly uploaded source maps. The process differs between EAS Build and EAS Update.

EAS Build

EAS Update

Code Change

Deploy Method

Native Build

Automatic Source Map Upload

Sentry Release Created

OTA Bundle

npx expo export

npx sentry-expo-upload-sourcemaps dist

Source Maps Uploaded

Symbolicated Errors in Dashboard

EAS Build (Automatic)

For native builds, source map upload is automatic. Just set the auth token as an EAS secret:

eas env:create --name SENTRY_AUTH_TOKEN --value "sntrys_YOUR_TOKEN_HERE" --scope project --visibility secret --environment production

That is it. The Sentry Expo plugin handles the rest during the build process.

EAS Update (Manual)

OTA updates do not automatically upload source maps. After every eas update, you need to upload them manually:

npx expo export --platform all
npx sentry-expo-upload-sourcemaps dist

Automate this in your CI/CD pipeline to avoid shipping updates with unreadable stack traces.

Step 8: Tagging EAS Update Metadata

When using OTA updates, tagging Sentry events with update metadata makes it possible to filter errors by update version:

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

function tagSentryWithUpdateInfo() {
  const manifest = Updates.manifest;
  const metadata =
    manifest && "metadata" in manifest ? manifest.metadata : undefined;
  const updateGroup =
    metadata && typeof metadata === "object" && "updateGroup" in metadata
      ? (metadata as { updateGroup: string }).updateGroup
      : undefined;

  const scope = Sentry.getGlobalScope();

  scope.setTag("expo-update-id", Updates.updateId ?? "embedded");
  scope.setTag("expo-is-embedded-update", String(Updates.isEmbeddedLaunch));

  if (updateGroup) {
    scope.setTag("expo-update-group-id", updateGroup);
  }
}

// Call this early in your app lifecycle, after Sentry.init()
tagSentryWithUpdateInfo();

This is particularly useful when an OTA update introduces a regression — you can immediately identify which update group caused the spike in errors.

Step 9: Verifying the Integration

Before going to production, verify that errors appear correctly in your Sentry dashboard with a simple test component:

import * as Sentry from "@sentry/react-native";
import { Button, View } from "react-native";

export function SentryTestButtons() {
  return (
    <View style={{ gap: 12, padding: 16 }}>
      <Button
        title="Test JS Error"
        onPress={() => {
          Sentry.captureException(new Error("Test JS error from Expo"));
        }}
      />

      <Button
        title="Test Unhandled Error"
        onPress={() => {
          throw new Error("Unhandled test error");
        }}
      />

      <Button
        title="Test Native Crash"
        onPress={() => {
          Sentry.nativeCrash();
        }}
      />
    </View>
  );
}

Check the Sentry dashboard after triggering each error. Verify that stack traces are properly symbolicated — you should see your original source file names and line numbers, not minified code.

Tip: The native crash test only works in development builds, not in Expo Go. If pressing the button does nothing, you are likely running in Expo Go.

Troubleshooting

Source Maps Show Minified Code

Cause: Source maps were not uploaded or the release/dist values do not match between the app and the uploaded maps.

Fix: Use automatic release naming (the default). If you set custom release or dist values in Sentry.init(), the automatic upload script cannot detect them. Either stick with defaults or manually upload source maps with matching release/dist values.

Cause: registerNavigationContainer was never called with the navigation ref.

Fix: Make sure useNavigationContainerRef() is called in the same component where Sentry is initialized, and register the ref in a useEffect.

Duplicate Error Reports in Development

Cause: React’s development mode rethrows errors caught by error boundaries to the global handler.

Fix: Test error boundary behavior in production builds. You can also disable Sentry in development with enabled: !__DEV__ if the noise is distracting.

sentry-expo Package Errors

Cause: The sentry-expo package was deprecated with the release of Expo SDK 50 and is no longer maintained.

Fix: Migrate to @sentry/react-native directly. Sentry provides a migration guide in their documentation.

High Event Volume Burning Through Quota

Cause: Sample rates set too high for production.

Fix: Use conservative rates: tracesSampleRate: 0.1-0.2, replaysSessionSampleRate: 0.1. Keep replaysOnErrorSampleRate: 1.0 since error replays are the most valuable.

Version Compatibility

ComponentMinimum VersionRecommended
@sentry/react-native7.x8.x (latest)
Expo SDK5052+
React Native0.73+0.76+
iOS Deployment Target15.0 (for v8)15.0+
Android Gradle Plugin7.4.0 (for v8)8.x

Note: Version 8 updates native SDKs (Cocoa v9, CLI v3, Android Gradle Plugin v6) with breaking changes in minimum version requirements. Check the release notes before upgrading.

Key Takeaways

  1. Use the wizardnpx @sentry/wizard@latest -i reactNative handles most setup automatically.
  2. Development builds are essential — Expo Go only captures JavaScript errors. Native crashes, frame tracking, and session replay require a development build.
  3. Secure your auth token — Never put SENTRY_AUTH_TOKEN in app.json. Use EAS secrets or environment variables.
  4. EAS Update needs manual source map upload — Unlike EAS Build, OTA updates do not automatically upload source maps. Automate sentry-expo-upload-sourcemaps in your CI pipeline.
  5. Use conservative sample rates in production — 10-20% for traces, 10% for session replays, 100% for error replays.
  6. Tag OTA updates — Use expo-updates metadata on Sentry scope so you can filter errors by update version.

References

Related posts

Multi-Channel Micro Frontends: Production Optimization and Cross-Platform Rendering

Advanced patterns for deploying micro frontends across mobile, web, and desktop. Performance optimization strategies, offline support, and production insights from scaling enterprise applications. Includes Rspack, Re.Pack, and alternative bundler approaches.

expoperformancere-pack+4
DynamoDB Rate Limiting: Strategies for Single Table Design at Scale

Practical strategies to prevent and handle DynamoDB throttling in Single Table Design applications. Covers partition key design, write sharding, capacity modes, DAX caching, retry patterns, and CloudWatch monitoring for high-throughput systems.

dynamodbawsrate-limiting+5
Comparing TypeScript Formatting and Linting Tools: Biome, Oxlint, ESLint, and Prettier

A comprehensive comparison of modern TypeScript linting and formatting tools - ESLint, Prettier, Biome, and Oxlint - with performance benchmarks, configuration examples, and migration strategies.

typescripteslintprettier+7
LangChain in Production: Patterns That Work and Anti-Patterns That Don't

Real lessons from deploying LangChain applications to production. Learn about the anti-patterns that cause failures and the patterns that enable success, with working code examples and cost optimization strategies.

langchainllmproduction+5
Database Query Profiling: Systematic Optimization Journey

How systematic database profiling and optimization reduced infrastructure costs significantly. PostgreSQL and MongoDB performance insights and practical patterns.

database-optimizationpostgresqlmongodb+7