Articles · How toUpdated October 2026

Adding crash reporting is two jobs, because a JavaScript error and a native crash are caught by different things.

Start with the half you already have. Apple collects crash reports from TestFlight and the App Store for every submitted version of your app, and you read them in the Crashes organizer in Xcode without installing anything. Google shows crashes and application not responding errors in Play Console. What neither store hands you is a readable JavaScript trace, so the work on your side is a reporting library in the app for the JavaScript side, plus a stored source map so the frames name your files instead of a byte offset. If you are earlier than that and still assembling the app itself, start at make an app.

Everything below comes from Apple, Expo, React Native and Google documentation, read on 22 October 2026, with the date each page carries printed next to anything that can move. One figure to fix in your head first: the switch that stores a source map for symbolication is off until you set it, on both platforms.

Start here

The short version

The stores watch the native crash. Your bugs are in the JavaScript.

Three steps, in this order. Read the store dashboards first, because they cost nothing and they are already collecting. Then add a reporting library so JavaScript errors reach you at all, since a thrown error in your bundle is invisible to both stores unless it kills the process. Then store a source map for every build you ship, which is the step people skip and then regret at two in the morning, reading a frame that says index.android.bundle, line 1, column 481231.

There is one more reason the free tooling matters more than it sounds. Apple points out that distribution builds, whether for the App Store, an enterprise or a testing team, do not carry the entitlements that let you debug them in Xcode. Once a build leaves your machine, reports and logs are the only instruments you have.

What is the difference between a JavaScript error and a native crash?

Whether the app is still alive when it happens. A JavaScript error is thrown inside your bundle while the process keeps running, which is why your own code can catch it, an error boundary can swap in a fallback screen, and a library can hold the report and send it later. Expo documents three capture paths for it. Unhandled errors are recorded automatically, because the library installs a global error handler the first time it is imported. Render errors are caught by an ObserveErrorBoundary around a subtree, which records the React component stack with them. Errors your code already catches reach neither of those, so you report them yourself with Observe.reportError.

A native crash is the opposite situation. Expo describes it as ending the app before your JavaScript can react to it, so the report gets written on the device and dispatched the next time the app launches. That sentence explains two things people file bugs about. An error boundary cannot help with a native crash, because there is no React left to render the fallback. And a crash you caused on purpose will not appear in any dashboard until you reopen the app, so a dashboard that looks empty five minutes after a test crash is behaving correctly.

The platforms disagree about what counts as a native crash, which matters when you are deciding what you can promise a customer. On Android, Expo records uncaught Java and Kotlin exceptions with their Caused by chain, and on Android 11 and later it reads the crash records the operating system keeps for the app, which is what brings in SIGSEGV and SIGABRT. On iOS it uses MetricKit, so you get Mach exceptions such as EXC_BAD_ACCESS and EXC_BREAKPOINT, signals such as SIGSEGV, SIGBUS and SIGTRAP, and on iOS 17 and later uncaught Objective-C and Swift exceptions. Two gaps are documented and worth quoting to anyone who expects full coverage: application not responding events and out of memory terminations are recorded on neither platform, and native crashes are not recorded on the iOS Simulator or on tvOS.

Expo documentation, Error reporting, modified 16 October 2026, read 22 October 2026

Check yours

What catches it, and what the trace looks like

Tick the failures you are trying to see in production. Every answer below comes from Expo's error reporting documentation, modified on 16 October 2026 and read on 22 October 2026. The feature is in preview and needs SDK 57 or later, with expo-observe 57.0.21 or later for the native half.

Nothing ticked. If you only ever tick the first one, you can stop after the source map step and skip the rest of this page.

What does Expo document for symbolicating a crash?

One mechanism for JavaScript, and an admission for native. In a production app your JavaScript is bundled and minified, so the frames describe positions in the generated bundle. A source map translates them back. Expo puts it in plain terms: when a source map is stored for a build or an update, the dashboard shows the original file, line and column for each frame, and links the build or update the error came from. When no source map is stored, it shows the trace as it arrived, with frames like index.android.bundle, line 1, column 481231, which Expo describes as difficult to map back to your code. React Native documents the same translation without a dashboard: pipe the trace through npx metro-symbolicate with the matching map.

Here is the part that catches people, and the claim worth checking before you trust a stack trace. React Native's Debugging Release Builds page, last updated 12 August 2026, says source maps are enabled by default on Android and disabled by default on iOS, where you add a SOURCEMAP_FILE entry to the Bundle React Native code and images build phase. That default is about generating a map while the native build runs. Storing one where a crash report can use it is a different switch, and Expo's eas.json reference, modified on 13 October 2026, says uploadSourceMaps defaults to false. Source map upload needs EAS CLI 22.0.0 or later and only works for builds that run on EAS servers, so a local build made with eas build --local uploads nothing. For an over the air update it is a flag, eas update --upload-source-maps, which needs EAS CLI 24.9.0 or later. The practical reading: assume you have no usable production trace on either platform until you have turned this on and watched a test error come back with a filename.

Native frames are a separate story and Expo says so directly: source maps only apply to JavaScript, and native stack traces are not symbolicated in the dashboard. On Android that costs you little, since Java and Kotlin frames already carry the class, method and line. On iOS, frames are resolved to symbol names on the device itself, so you see function names with no file and no line, and a frame that cannot be resolved arrives as a binary name and an offset such as MyApp + 19160. Uploading debug symbols, meaning Android ProGuard mappings and iOS dSYMs, is listed as not supported yet. That is the gap Xcode fills, which is the next section. Localised formatting is a common trigger here, so if a crash is confined to one country, read add localization before you go hunting in the native layer.

Expo documentation, Error reporting, symbolicated stack traces, read 22 October 2026

What does Apple give you in Xcode Organizer for free?

More than most people install a third-party tool to get. Apple states that TestFlight and the App Store collect crash reports for every submitted version of your app, and that you use the Crashes organizer in Xcode to get a report about an issue a customer reported. The reports carry real symbol names automatically if you included symbol information when you submitted the build. Select a crash and the Inspector offers Generate Recommendations, which opens your project and pastes the stack trace into the coding assistant to help you find the cause.

The sentence worth memorising concerns who you hear from. The Crashes organizer presents reports from customers who share diagnostic and usage information, so your App Store population is a self-selected subset. TestFlight users, Apple says, share crash reports with you automatically, regardless of the device settings for sharing diagnostic and use data. Your beta testers are the only group that reports without being asked, which is a strong argument for keeping a real test group rather than treating TestFlight as a formality before release. If the organizer stays empty, Apple has a dedicated help page on enabling collection from customers.

What you cannot get there is a short, specific list, and knowing it saves an afternoon of scrolling. Four report types never appear in the Crashes organizer at all, and the table below says where each one lives instead. One practical note on sharing: if you need help with a crash, Apple says to use its own crash reports rather than third-party ones, because a third-party report may omit information, and to symbolicate before sharing or the report shows hexadecimal addresses instead of function names.

The reportIn the Crashes organizerWhere to get it instead
App crash from an unhandled exception or a signalYesCollected by TestFlight and the App Store for every submitted version of your app.
Watchdog event, such as a slow app launchNoOff the device. Settings, then Analytics and Improvements, then Analytics Data, then share the log.
Invalid code-signature crashNoOff the device, or by connecting the device to a Mac and reading its logs in Xcode.
Thermal event, where a device overheats because an app uses too much CPUNoOff the device, the same way as a watchdog event.
Jetsam event, where an app has high memory useNoOff the device. The log name starts with JetsamEvent_ and the date, rather than your binary name.
A problem in your app that is not a crashNoConnect the device to a Mac, open the Console app, select the device, reproduce the problem and note the time.

Apple, Acquiring crash reports and diagnostic logs, read 22 October 2026

What should you turn on first, and what can wait?

Store a source map before you choose a vendor. It is one line in a build profile, it applies to every error you will ever receive from that build, and without it the rest of the setup produces traces nobody can act on. Second, pick the reporter and ship one deliberate error through it, then reopen the app and confirm the report arrives. Expo is clear that a fatal error is reported on the app's next launch, so a recent crash takes time to appear. The thing you are testing is the arrival, and it needs that extra launch to happen.

Third, read the two store dashboards once a week and learn what your normal looks like. Google notes that user-perceived crash rate is a core vital, and that it affects how discoverable your app is on Google Play, so an Android crash rate is a distribution problem as well as an engineering one. Expo's own debugging guide adds the cheapest local reproduction trick on this page: run npx expo start --no-dev --minify to execute your bundle the way production does, which surfaces errors that development mode hides.

What can wait is dashboards for everything. A crash that only affects a handful of sessions on one old device model is a different problem from one that affects every user on the current OS, and the breakdown by version, device and country tells you which you have. A crash confined to a small group of users is often a settings difference rather than a device fault, so larger text and screen reader paths are worth testing early, which is the ground covered in add accessibility.

One last piece of sequencing, because it decides whether any of this is available to you. All of the above assumes you hold the project and the developer accounts. Newly describes the app in plain English and writes a real React Native and Expo project you own, with two-way GitHub sync, so a reporting library is a dependency you add in your own repository. It uploads iOS builds to TestFlight, publishes to Google Play internal testing and builds a standalone Android APK, from $25 a month with no free tier. Apple guideline 4.2.6 requires the provider of the app's content to submit, so the submission itself stays yours, and the steps for it are in publish to app store.

Apple, Acquiring crash reports and diagnostic logs, on collecting reports and symbol names, read 22 October 2026

Questions people ask about crash reporting

For native crashes, not on day one. Apple's documentation says TestFlight and the App Store collect crash reports for every submitted version of your app, and you read them in the Crashes organizer in Xcode. Google says Play Console shows crashes and application not responding errors under Android vitals, from devices whose users opted in to share usage and diagnostics data. What neither store gives you is a JavaScript stack trace that points at your own files, and in a React Native app that is where most of your own bugs live. Both checked on 22 October 2026.

Store the source map, then throw one error on purpose.

A crash reporting setup you have never seen a report come out of is not a setup. Ship a build with the map stored, throw a deliberate error from a button, reopen the app, and read the frame. If it names a file of yours, you are done.

Start building