FireLogGet the app

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. 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. 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. 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. 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 + 40012

Useless. 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() + 40

Same 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.

TypeWhat happenedDid the app close?
CrashAn unhandled error killed the processYes, instantly
Non-fatalYou caught an error and reported it deliberatelyNo
ANRAndroid: the main thread froze for ~5 secondsThe 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.

crashlytics().setCustomKey("screen", "checkout");
crashlytics().setCustomKey("plan", "premium");
crashlytics().log("User tapped Pay");
crashlytics().setUserId(internalUserId);

Setup, by platform

PlatformWhat it takes
AndroidAdd the com.google.firebase.crashlytics Gradle plugin. dSYMs are not a concern here — Gradle uploads mapping files automatically.
iOSAdd the SDK, then a build phase script that uploads dSYMs on every build. Skip the script and every report is unreadable.
Flutterfirebase_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.
WebNot 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.

Keep reading