React Native Firebase with Expo (2026): Analytics, Crashlytics, and Remote Config
A practical, production-tested guide to wiring @react-native-firebase v22+ into an Expo project: config plugin setup, type-safe Analytics, Crashlytics inside your error boundary, realtime Remote Config, and EAS Build hooks that stop google-services drift.
React Native Firebase with Expo works today by pairing @react-native-firebase v22+ with its official Expo config plugin: you install the modular packages, drop google-services.json and GoogleService-Info.plist into the repo, register the plugin in app.config.js, and build a custom dev client with EAS. So let's dig into the Analytics, Crashlytics, and Remote Config setup my fintech platform team actually ships to production, including the type-safe wrappers, EAS Build hooks, and CI guards we use to stop google-services files from drifting between staging and prod.
@react-native-firebase v22+ requires a custom dev client and prebuild. It can't run in Expo Go because it depends on native code.
Register the official config plugin in app.config.js and reference googleServicesFile paths per platform. Keep the plaintext files out of git and inject them via EAS Secret files.
Crashlytics needs dSYM upload during archive; EAS Build enables this automatically once the Fastlane run script is preserved in the iOS project.
Remote Config gained realtime listeners in 2025, so wire onConfigUpdated to Zustand and stop polling for feature flags.
Firebase App Check with DeviceCheck (iOS) and Play Integrity (Android) belongs at the edge of every authenticated API call, not just at login.
The Firebase JS SDK works in Expo Go, but you lose Crashlytics, Analytics offline queueing, and App Check attestations. Treat it as a prototype path only.
Why Firebase with Expo in 2026
Firebase has been the default backend-as-a-service for mobile teams since the mid-2010s. After a rocky few years where "does Firebase work with Expo?" was a top Stack Overflow question every quarter, the answer in 2026 is a clean yes. Both Invertase's @react-native-firebase monorepo and the Expo docs converge on the same recipe now: a bare Expo project with prebuild, a config plugin, and platform-specific service files. The React Native Firebase documentation is the source of truth for supported modules and version compatibility, and Expo publishes a companion guide that covers the config plugin quirks.
Honestly, on the fintech side, we picked @react-native-firebase over the pure JS SDK for three reasons that keep coming up in architecture reviews. First, Crashlytics ingests native crashes. A Swift or Kotlin crash on a payment screen would be invisible to a JS-only SDK, which only sees JS errors. Second, Analytics is required by our attribution partners in its native form so DebugView, Google Ads conversions, and BigQuery streaming all work as documented. Third, App Check attestations rely on DeviceCheck and Play Integrity APIs that require native access. The JS SDK is fine for prototypes; anything production-facing on a regulated app needs the native modules.
If you're weighing this decision alongside your broader observability strategy, I've already covered the crash-handling half in React Native error handling with Sentry. Crashlytics and Sentry aren't mutually exclusive. Most fintechs I know run both and let the finance team pick which dashboard they trust. Crashlytics is free, so the ROI math is easy.
Can you use Firebase with Expo? Install and configure the plugin
Yes. Firebase works with Expo, but only in a bare workflow with a custom dev client. Expo Go ships a fixed set of native modules and Firebase isn't among them. The path is: expo prebuild, install the packages, register the config plugin, add the platform service files, and run eas build --profile development. Once you have that dev client on your simulator you never touch Xcode again for day-to-day work.
Start by installing the app package plus whatever modules you plan to use. For this guide we'll cover Analytics, Crashlytics, and Remote Config, so:
Then register the config plugin. If you're using app.json it's a string entry, but I prefer app.config.ts because it plays nicely with environment variables:
Two footguns are worth calling out (I hit both of them shipping our first build). The useFrameworks: 'static' line is non-negotiable on iOS, because the Firebase Cocoapods only ship static frameworks and CocoaPods will refuse to install otherwise. And googleServicesFile paths must be strings that Expo can resolve at prebuild time, which means you either commit dummy staging files or use EAS Secret files that get materialized before build. I'll cover the secrets flow in the EAS section below.
After registration, run npx expo prebuild --clean to regenerate the native projects, then eas build --profile development --platform all. Install the resulting builds on your simulators and open them with the dev-client. If you see a "no Firebase app '[DEFAULT]'" error at runtime, the service file didn't land in the right place. Check the prebuild logs for the "Configuring GoogleService-Info.plist" line.
Firebase Analytics with type-safe events
Analytics is the easiest module to get wrong and the hardest to fix later. The API accepts any event name and any parameter, which means a team of five engineers left alone with logEvent will produce thirty variants of "checkout_started" within a quarter. My rule: nobody calls @react-native-firebase/analytics directly. Every event goes through a typed wrapper that lives in the shared platform package.
Now track('checkout_started', { cart_id: '...', value_cents: 4200, currency: 'USD' }) is autocompleted and typo-safe. TypeScript will refuse a rogue chekout_starte, and the parameter shape is enforced at compile time. When your data science team wants a new event, they open a PR against the EventMap, code review lands schema and code in one atomic commit, and BigQuery queries never need to guard for missing fields.
Screen tracking deserves its own note. If you're on Expo Router, wire the tracker in _layout.tsx:
For debugging in development, run adb shell setprop debug.firebase.analytics.app com.example.ledger on Android, or add -FIRAnalyticsDebugEnabled to the iOS scheme's arguments. Events then stream to Firebase DebugView in near-real-time. Without that flag Analytics batches events every hour, which turns "did my event fire?" into a slow guessing game.
Crashlytics inside your error boundary
Crashlytics for React Native captures three things: native crashes automatically, JS crashes via crashlytics().recordError, and custom logs and keys via log and setAttribute. The trick is wiring the JS side to your existing React error boundary so a component crash surfaces in the dashboard with breadcrumbs.
Wrap the boundary around the root of your Expo Router tree. For unhandled promise rejections, register a global handler in the same startup file so those don't slip past the boundary. And set user-scoped attributes early (user id, subscription tier, dev-build indicator) so the Crashlytics dashboard can filter by segment.
Two operational notes. First, Crashlytics dedupes crashes by stack fingerprint, so if you catch and rethrow errors you'll fragment reports. Log breadcrumbs (crashlytics().log(...)) instead of wrapping. Second, on iOS the initial dSYM upload after a release can take up to 12 hours to process, so if you see "missing dSYM" for a build, wait one business day before opening a support ticket.
Remote Config, realtime updates, and feature flags
Remote Config in 2026 is meaningfully better than it was two years ago because the SDK now supports realtime listeners. The server pushes changes over a persistent connection instead of waiting for the client to fetch. That single change removes the "why isn't my flag flipping" support ticket from your queue.
Components read useFlags(state => state.new_checkout_flow) and get automatic re-renders when the server pushes an update, with no polling loop and no refresh button. If you're already using Zustand for local state, this fits without adding a dependency. If you use Jotai, the equivalent is a single atom updated inside the listener. For a broader look at picking a store, see modern state management with Zustand and TanStack Query.
Two anti-patterns to avoid. Don't call remoteConfig().getValue() inside a render, because the SDK is not designed for that access pattern and you'll leak sync calls into your bridge. And don't use Remote Config to store secrets. It ships to every install of your app; treat every value as public.
EAS Build, dSYMs, and google-services drift
The two operational failure modes I see most often are: staging builds using production google-services files, and iOS crash reports showing up as "unknown" because dSYMs never uploaded. Both are fixable with about ten lines of EAS configuration.
For service file management, put both files in EAS Secret files, one pair per environment. Reference them in eas.json:
Then in app.config.ts the paths already read from those environment variables, so a staging build can't accidentally ship production Firebase credentials. If you're managing several profiles you'll want the broader Expo environment variables and EAS Secrets workflow, which covers rotation and CI injection.
For dSYMs, EAS Build handles the upload automatically as long as the Fastlane run script phase installed by the Firebase config plugin is intact. Run eas build --clear-cache once after upgrading @react-native-firebase to a new major to make sure the script phase is refreshed. To verify, download a completed iOS build and check that the archive includes Firebase/upload-symbols in the Build Phases list.
Config plugin authoring is out of scope here, but worth referencing. If you need to modify the generated Info.plist or AndroidManifest.xml beyond what the Firebase plugin provides, the mechanics are covered in the Expo config plugins guide. That saved me a full afternoon on my last project.
Firebase JS SDK vs @react-native-firebase
The most common Stack Overflow question I see is "should I use the JS SDK or the native one?" Here's the comparison I use in architecture reviews:
Capability
Firebase JS SDK
@react-native-firebase
Works in Expo Go
Yes
No (dev client required)
Native crash reporting
No
Yes (Crashlytics)
Analytics DebugView
Limited
Full support
App Check (DeviceCheck / Play Integrity)
Web attestation only
Yes
Bundle size (Auth + Firestore)
~200 KB gzipped JS
~2 MB native
Realtime Remote Config listener
Yes
Yes
Setup complexity
npm install and go
Config plugin + prebuild
Recommended for production
Prototypes only
Yes
Short answer: use the JS SDK when you're spiking an idea inside Expo Go and can't be bothered with a dev client. Use @react-native-firebase for anything that will ship to a store. The bundle size gap is real but immaterial next to the crash reporting and App Check functionality you lose with the JS path.
One hybrid pattern that sometimes makes sense: use @react-native-firebase for Analytics, Crashlytics, App Check, and Messaging, but call Firestore and Auth from your backend rather than the client. That gives you server-side control over data access rules without the client having to authenticate Firebase, which is a good fit when your primary API is already GraphQL or REST.
Frequently Asked Questions
Does @react-native-firebase work with Expo managed workflow?
Yes, as of Expo SDK 51+ the managed workflow supports @react-native-firebase through its official config plugin and Continuous Native Generation. You still need a custom dev client instead of Expo Go, but you never have to touch the Xcode or Android Studio projects manually.
Can I run Firebase Analytics in Expo Go?
Not with @react-native-firebase/analytics, since it requires native code that Expo Go doesn't include. The Firebase JS SDK's Analytics module works in Expo Go but only records web-style events and misses the native features Google Ads and BigQuery integrations expect.
How do I test Crashlytics in development?
Force a native crash on demand with crashlytics().crash() in a hidden dev-only button. Crashes only upload after the app is relaunched, so trigger the crash, reopen the app, and check the Crashlytics dashboard about 5 to 10 minutes later. Debug builds are filtered out by default; toggle "collection enabled" via crashlytics().setCrashlyticsCollectionEnabled(true) to test.
What's the difference between google-services.json and GoogleService-Info.plist?
They're the same data in different formats, one for Android and one for iOS. Both contain the Firebase project id, app id, API key, and sender id for each platform. You need both files if your app targets both platforms, and each is specific to a single Firebase app (bundle id or package name).
Do I need Fastlane to upload dSYMs for Crashlytics?
No. EAS Build runs the Firebase-provided upload script automatically during archive as long as the Crashlytics config plugin is registered. Fastlane is only needed if you're managing releases outside EAS. If uploads fail, check that useFrameworks: 'static' is set in expo-build-properties.
A hands-on Reassure guide for React Native and Expo: Jest setup, measurePerformance vs measureFunction, CI baseline diffs, and a Danger.js budget that actually catches regressions before merge.