Expo + Sentry: How to Set Up Error Tracking in React Native (Complete Guide)
March 2026
Sentry catches crashes, unhandled exceptions, and performance issues in your React Native app before your users complain about them. With Expo, setup takes about 10 minutes using @sentry/react-native and the expo-sentry plugin. You install the package, add it to your app config, initialize it with your DSN, and you immediately start seeing every crash, slow screen, and failed API call in a single dashboard.
This guide covers the full setup process, source map configuration with EAS Build, custom error boundaries, performance monitoring, and how Sentry compares to Crashlytics and BugSnag for React Native projects.
Table of Contents
Why You Need Error Tracking
I learned this the hard way. I shipped an update to one of my apps that looked completely fine on my iPhone 15 Pro and my Pixel 7 test device. All screens loaded, all flows worked, no warnings in the console. I pushed the update through EAS and went to bed.
The next morning, Sentry had flagged 47 crashes -- all from Samsung Galaxy A series devices running Android 12. A specific API response was returning a slightly different data shape than I expected, and my parsing logic threw a null reference error that crashed the app on launch. Without Sentry, I would have found out about this from one-star reviews. Instead, I had the exact stack trace, the device model, the OS version, and the API response that caused it. I pushed a fix within an hour.
The reality of mobile development is that you cannot test every device and OS combination. You cannot reproduce every network condition your users experience. You cannot anticipate every edge case in third-party API responses. Error tracking is not a nice-to-have -- it is how you find out what is actually happening in production.
Setting Up Sentry with Expo
The setup is straightforward. Start by installing the Sentry React Native SDK:
npx expo install @sentry/react-native
Next, add the Sentry Expo plugin to your app.json (or app.config.js):
{
"expo": {
"plugins": [
[
"@sentry/react-native/expo",
{
"organization": "your-org-slug",
"project": "your-project-slug"
}
]
]
}
}
Now initialize Sentry in your app entry point. If you are using Expo Router, this goes in your root _layout.tsx:
import * as Sentry from "@sentry/react-native";
Sentry.init({
dsn: "https://your-dsn@sentry.io/your-project-id",
tracesSampleRate: 1.0,
debug: __DEV__,
enabled: !__DEV__,
});
A few things to note here. The dsn is the unique identifier for your Sentry project -- you get this from the Sentry dashboard when you create a new project. Setting enabled: !__DEV__ means Sentry only runs in production builds, so you are not flooding your dashboard with development errors. The tracesSampleRate at 1.0 captures 100% of performance traces -- you may want to lower this if you have high traffic to stay within your plan limits.
If you are wrapping your root component, Sentry provides a higher-order component:
export default Sentry.wrap(RootLayout);
This wraps your entire app in Sentry's error boundary and enables automatic screen tracking for React Navigation or Expo Router.
That is it for the basic setup. Build with eas build and Sentry starts capturing errors from your production app.
What Sentry Actually Catches
Out of the box, without any additional configuration, Sentry captures:
Native crashes -- both iOS and Android native-level crashes that would otherwise just show a generic "app has stopped" dialog. These include out-of-memory crashes, native module failures, and segfaults.
JavaScript exceptions -- any unhandled error in your React Native JavaScript thread. This covers rendering errors, undefined property access, type errors, and syntax issues that somehow slipped through.
Unhandled promise rejections -- async operations that fail without a catch block. This is one of the most common sources of silent failures in React Native apps. An API call fails, the promise rejects, nothing catches it, and your user sees stale data with no indication of what went wrong.
Breadcrumbs -- Sentry automatically records a trail of events leading up to each error: network requests, console logs, navigation events, user interactions. When you look at a crash report, you see exactly what the user did in the 30 seconds before things broke.
User sessions -- Sentry tracks session health, giving you a crash-free session rate. This is the single most important metric for app stability. If your crash-free rate drops below 99%, you have a serious problem.
Performance traces -- with tracesSampleRate enabled, Sentry captures app start time, screen render duration, and HTTP request performance. More on this in the performance section below.
Source Maps with EAS Build
This is the part most guides skip, and it is the difference between a useful Sentry setup and a frustrating one. Without source maps, your JavaScript stack traces look like this:
TypeError: Cannot read property 'name' of undefined
at o (bundle.js:1:234567)
at s (bundle.js:1:345678)
That is useless. You need source maps so Sentry can show you the actual file, function name, and line number.
With the Sentry Expo plugin configured in your app.json (as shown above), source map uploads happen automatically during EAS Build. The plugin hooks into the build process and uploads source maps to Sentry using your organization and project configuration.
You need to set your Sentry auth token as an EAS secret:
eas secret:create --name SENTRY_AUTH_TOKEN --value your-auth-token
You can generate this token in Sentry under Settings > Auth Tokens. Make sure it has the project:releases and org:read scopes.
For the plugin to associate source maps with the correct release, it uses the app version and build number automatically. Each EAS Build generates a unique release identifier that Sentry uses to match stack traces to the correct source maps.
To verify that source maps are uploading correctly, check the Sentry dashboard under Releases after your build completes. You should see a new release with source map artifacts attached. If the artifacts are missing, check your EAS build logs for any Sentry-related warnings.
One common gotcha: if you are using expo-updates for over-the-air updates, you need to make sure source maps are uploaded for each OTA update as well, not just for each native build. The Sentry plugin handles this when you run eas update, but only if your auth token is configured correctly.
Custom Error Boundaries
Sentry's automatic error catching is great, but you should also set up custom error boundaries for specific parts of your app. This lets you show a graceful fallback UI instead of a white screen when something goes wrong.
Here is a basic error boundary that reports to Sentry:
import React from "react";
import * as Sentry from "@sentry/react-native";
import { View, Text, TouchableOpacity } from "react-native";
class ErrorBoundary extends React.Component<
{ children: React.ReactNode; fallback?: React.ReactNode },
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
Sentry.captureException(error, {
extra: { componentStack: errorInfo.componentStack },
});
}
render() {
if (this.state.hasError) {
return (
this.props.fallback || (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>Something went wrong.</Text>
<TouchableOpacity onPress={() => this.setState({ hasError: false })}>
<Text>Try Again</Text>
</TouchableOpacity>
</View>
)
);
}
return this.props.children;
}
}
I wrap individual screens or feature sections rather than just the root. This way, if your profile screen crashes, the user can still navigate to other tabs. A single root-level error boundary means any error anywhere takes down the entire app UI.
You can also use Sentry's built-in Sentry.ErrorBoundary component, which does the reporting automatically:
<Sentry.ErrorBoundary fallback={<FallbackScreen />}>
<ProfileScreen />
</Sentry.ErrorBoundary>
For critical flows like purchases or onboarding, I add manual Sentry breadcrumbs so I can trace exactly where in the flow the error occurred:
Sentry.addBreadcrumb({
category: "purchase",
message: "User tapped subscribe button",
level: "info",
});
Performance Monitoring
Sentry's performance monitoring gives you visibility into how your app actually feels for users, not just whether it crashes.
The metrics that actually matter:
App start time -- how long from the user tapping your icon to seeing the first meaningful screen. Sentry splits this into cold start (app was not in memory) and warm start (app was backgrounded). If your cold start exceeds 3 seconds, you are losing users.
Screen load time -- how long each screen takes to render with data. Sentry captures this automatically when you use Sentry.wrap() with React Navigation or Expo Router. You can see which screens are slow and investigate why.
HTTP request duration -- Sentry instruments fetch automatically and tracks how long each API call takes. You can see average response times, P95 latency, and failure rates broken down by endpoint.
To add custom performance spans for operations you care about:
const transaction = Sentry.startTransaction({
name: "generate-ai-image",
op: "task",
});
try {
const result = await generateImage(prompt);
transaction.setStatus("ok");
} catch (error) {
transaction.setStatus("internal_error");
Sentry.captureException(error);
} finally {
transaction.finish();
}
In practice, I find the HTTP request tracking most valuable. It immediately shows you when a backend endpoint starts degrading, often before you notice it yourself. Combine that with error tracking and you get the full picture: this endpoint started returning 500s at 3am, which caused 200 unhandled exceptions on the client side.
Sentry vs Crashlytics vs BugSnag
All three are solid tools. Here is how they compare for React Native with Expo:
| Feature | Sentry | Crashlytics | BugSnag |
|---|---|---|---|
| Expo integration | Native plugin, first-class support | Manual setup, no Expo plugin | Community plugin available |
| Source maps | Automatic with EAS Build | Manual upload required | Automatic with CLI |
| Breadcrumbs | Automatic + custom | Limited automatic | Automatic + custom |
| Performance monitoring | Built-in, detailed traces | Limited to startup time | Separate product (extra cost) |
| Free tier | 5K errors/month, 10K transactions | Unlimited (part of Firebase) | 7.5K events/month |
| React Native SDK | Official, actively maintained | Via React Native Firebase | Official SDK |
| Stability score | Release health dashboard | Crash-free users metric | Stability score (their flagship feature) |
| Alerting | Flexible rules, Slack/email/PagerDuty | Basic Firebase alerts | Flexible rules, integrations |
Sentry wins for Expo and React Native projects because of the native plugin, automatic source map uploads with EAS Build, and the most detailed breadcrumb trails. The performance monitoring is included in the base product and gives you genuine visibility into app responsiveness.
Crashlytics wins if you are already deep in the Firebase ecosystem. The unlimited free tier is hard to beat, and if you are using Firebase Auth, Firestore, and Cloud Messaging, adding Crashlytics is a one-line integration. The tradeoff is that setup with Expo requires more manual configuration and source map uploads are not automated.
BugSnag wins on its stability score feature, which gives you a single metric that reflects the percentage of sessions that are error-free, weighted by severity. It is the most polished stability metric of the three. The downside is that performance monitoring is a separate, paid add-on.
For most React Native developers building with Expo, Sentry is the best choice. The integration is the smoothest, the source map story is the most complete, and you get error tracking and performance monitoring in one product.
What I Track in Production
Here are real examples from my production apps, AIVidly and Newsletterytics, that show the kind of issues Sentry surfaces.
API failures with context -- in AIVidly, I track every AI generation request with custom breadcrumbs. When OpenAI's API returns a timeout or rate limit error, I capture it with the prompt length, model used, and retry count. This showed me that prompts over 2000 tokens were timing out 15% of the time with a specific model, so I added automatic prompt truncation and model fallback logic.
AI generation timeouts -- generating images and processing audio can take 30+ seconds. I set up custom transactions to measure generation time by type. Sentry showed me that text-to-speech requests were averaging 8 seconds but spiking to 45 seconds during peak hours. I added a loading timeout with a retry mechanism and a user-facing message explaining the delay.
Purchase flow errors -- in Newsletterytics, the subscription flow goes through RevenueCat. I add breadcrumbs at each step: paywall shown, product selected, purchase initiated, purchase completed (or failed). When purchases started failing for a subset of Android users, Sentry showed me the exact RevenueCat error code and the step where it failed. It turned out to be a Google Play billing library version mismatch that only affected devices with an older Play Store version.
Navigation dead ends -- I track screen transitions and capture an error if a user hits the same screen more than three times in a row within 10 seconds (which indicates they are stuck in a loop). This caught a bug where a deep link was routing users back to the same screen repeatedly.
These are not hypothetical scenarios. Every one of these was a real production issue that I found through Sentry before users reported it. That is the value of proper error tracking.
When Sentry Is Overkill
Sentry is not always the right choice. Here is when you can skip it:
Hobby projects and prototypes -- if you are building something for yourself or experimenting with a new idea, you do not need a full error tracking setup. You will see the crashes in your development console. Add Sentry when the app goes to real users.
Apps with fewer than 100 users -- at very low user counts, you can often handle bug reports through direct user feedback. The overhead of configuring Sentry, managing source maps, and monitoring a dashboard is not worth it when you can just ask your 50 users what happened.
If you are already deep in Firebase -- if your entire backend is Firebase, you are using Crashlytics, and everything works, there is no compelling reason to switch to Sentry. Crashlytics is free, it integrates tightly with your existing Firebase setup, and the crash reporting is solid even if the breadcrumbs and performance monitoring are less detailed.
Very early development -- do not add Sentry on day one of a new project. Get your core features working first. Add error tracking when you are preparing for your first production release or beta test.
For everyone else -- if you have a production app with real users and you want to catch problems before they become one-star reviews -- Sentry is worth the setup time.
How Ship React Native Handles Error Tracking
Ship React Native includes Sentry pre-configured out of the box. The Expo plugin is set up in the app config, initialization happens in the root layout, and source maps are configured to upload automatically with every EAS Build and EAS Update.
The template also includes custom error boundaries wrapping each tab screen, so a crash in one section does not take down the entire app. Performance monitoring is enabled with sensible defaults -- 100% trace sampling for small apps that you can dial down as your user base grows.
The purchase flow, AI generation functions, and API calls all have Sentry breadcrumbs and custom transactions built in. When something goes wrong in production, you see exactly what happened, on which device, and what the user was doing at the time.
Instead of spending an afternoon configuring Sentry, uploading test source maps, and verifying that everything connects properly, you start with a working setup and focus on building your app.
Related posts:
- Convex for React Native: Why I Switched from Supabase -- the backend where these errors originate
- How to Monetize a React Native App -- protecting revenue by catching bugs early
- React Native Templates in 2026 -- templates that come with error tracking built in