Firebase Crashlytics: How Crash Reporting Actually Works
When your app crashes on someone else's phone, Crashlytics is how you find out — and how you find out why.
8 min read · updated August 5, 2026
Your app crashes on a stranger's phone in another country. They do not email you. They uninstall. Crashlytics is how you find out it happened, and what line of code was responsible.
It is free and unlimited, on every platform Firebase supports.
What happens at the moment of a crash
- 1
The app dies
Something goes wrong badly enough that the operating system stops the app. Crashlytics has already installed a handler for exactly this moment.
- 2
A report is written to disk
It cannot upload immediately — the app is in the middle of dying. Instead it writes a small report locally: the stack trace, device model, OS version, available memory, and how long the app had been running.
- 3
The next launch sends it
When the user opens the app again, the report uploads. This is why crash counts lag a little behind reality, and why a crash on launch can take a while to show up.
- 4
Crashlytics groups it
Identical crashes from thousands of users collapse into one issue with a count. You see "this crash, 4,812 users" rather than 4,812 separate reports.
Why iOS crashes arrive as gibberish
This is the most-searched Crashlytics problem, and the answer is not obvious.
When Xcode builds your app, it strips out human-readable names to make the binary smaller. Function names become memory addresses. A crash report from that build looks like this:
0 MyApp 0x0000000104a3c8f4 0x104a30000 + 51956
1 MyApp 0x0000000104a3b1a0 0x104a30000 + 45472
2 MyApp 0x0000000104a39c2c 0x104a30000 + 40012Useless. The translation table lives in a separate file called a dSYM — debug symbols. Upload the dSYM and Crashlytics turns those addresses back into real names:
0 MyApp CartViewController.applyDiscount(code:) + 84
1 MyApp CartViewController.checkout() + 212
2 MyApp CheckoutButton.tapped() + 40Same crash. Now you know where to look.
Crashes, non-fatals, and ANRs
Crashlytics reports three different kinds of bad, and they are worth telling apart.
| Type | What happened | Did the app close? |
|---|---|---|
| Crash | An unhandled error killed the process | Yes, instantly |
| Non-fatal | You caught an error and reported it deliberately | No |
| ANR | Android: the main thread froze for ~5 seconds | The system offered to close it |
Non-fatals are the underused one. When you catch an error and recover gracefully, the user sees nothing — and neither do you, unless you record it.
try {
await syncBasket();
} catch (error) {
// The user gets a retry button rather than a crash.
showRetry();
// You still find out it happened.
crashlytics().recordError(error);
}Making reports easier to debug
A stack trace tells you where. These tell you why.
- Custom keys. Attach state to the report — which screen, which feature flags, whether the user was signed in. When a crash only happens for premium users, a key makes that obvious immediately.
- Log messages. A breadcrumb trail of the last actions before the crash. Enough to reconstruct what the person was doing.
- User identifiers. Attach an ID so you can connect a support email to a specific crash. Use an internal ID, not an email address — this data goes to a third party.
crashlytics().setCustomKey("screen", "checkout");
crashlytics().setCustomKey("plan", "premium");
crashlytics().log("User tapped Pay");
crashlytics().setUserId(internalUserId);Setup, by platform
| Platform | What it takes |
|---|---|
| Android | Add the com.google.firebase.crashlytics Gradle plugin. dSYMs are not a concern here — Gradle uploads mapping files automatically. |
| iOS | Add the SDK, then a build phase script that uploads dSYMs on every build. Skip the script and every report is unreadable. |
| Flutter | firebase_crashlytics, and route FlutterError.onError into it so framework errors are captured too. |
| React Native | @react-native-firebase/crashlytics, plus a source map upload step for readable JavaScript traces. |
| Web | Not supported. Crashlytics is mobile only — use Firebase Performance Monitoring or a browser error tracker instead. |
That last row surprises people who search for browser crash reporting in Firebase. There is not any.
Reading the numbers
The metric that matters is crash-free users — the percentage of people who used your app today without it dying. Not crash count, which moves with how many users you have.
Above 99% is healthy for most apps. Below 98% and something is wrong for roughly one user in fifty, which is enough to show up in your reviews.
Sort by users affected, not by how recent a crash is. The scariest-looking new crash is often one person on a rooted phone from 2015.
Frequently asked questions
- Is Firebase Crashlytics free?
- Yes, entirely, with no cap on the number of reports. It is one of the few Firebase services with no paid tier at all.
- What is a dSYM file and why does Crashlytics need it?
- A dSYM holds the debug symbols Xcode strips out of your compiled app. Without it, iOS crash reports show memory addresses instead of function names. Uploading it lets Crashlytics translate those addresses into readable code locations.
- Why does Crashlytics say a dSYM is missing?
- Because a build was uploaded without its symbol file — often with Bitcode, where Apple recompiles the app and generates new dSYMs you never had. Download them from App Store Connect and upload them. Every build produces a new dSYM.
- How long do crash reports take to appear?
- Reports upload on the next app launch, not at the moment of the crash, since the app is dying at that point. Expect minutes to hours depending on how quickly people reopen your app.
- What is a non-fatal error?
- An error you caught and handled, but reported anyway. The app kept running and the user may not have noticed, but you still find out it happened.
- Does Crashlytics work on web apps?
- No. Crashlytics supports iOS, Android, Flutter and React Native only. For web you need Firebase Performance Monitoring or a separate browser error tracking service.