Automated Firebase setup for React Native (Expo & Bare)
- @cardor/rn-firebase-cli
- Table of Contents
- What is this?
- Features
- Prerequisites
- Installation
- Quick Start
- CLI Commands Reference
- Configuration
- Multi-Environment Setup
- Auto-generated package.json scripts
- Usage Examples
- Project Structure
- API
- Development
- Limitations
- License
- About
Setting up Firebase in a React Native project is repetitive and error-prone. You need to:
- Create Firebase apps in the Firebase Console
- Download
google-services.jsonandGoogleService-Info.plist - Configure
app.json(Expo) or native build files (Bare RN) - Create environment-specific config files
- Update
.gitignoreso you don't commit secrets
Do this across multiple environments (dev, staging, prod) and it becomes a chore.
@cardor/rn-firebase-cli automates all of it. One command downloads your config files, extracts the right values, wires them into your project, and generates reusable config for your app code.
- Interactive wizard — Guided prompts for project selection, platforms, environments
- Non-interactive mode — All options available as CLI flags for CI/CD pipelines
- Auto-detection — Detects Expo vs Bare RN, reads bundle IDs from
app.json - Firebase API integration — Lists projects, verifies apps, downloads config via
firebase-tools - Multi-environment — Supports dev, staging, prod (or custom names) in a single config
- Web client ID extraction — Automatically extracts the OAuth web client ID from
google-services.json - Env file generation — Writes all Firebase vars to
.env.{envName}with the correct prefix per project type - Expo fully supported — Writes
googleServicesFilepaths intoapp.json, generatesconfig/firebase.config.* .gitignoremanagement — Adds the output directory and.env.*pattern to.gitignoreautomatically- Status check — See at a glance which Firebase files are configured
- Update command — Re-download config files after adding apps or changing projects
- Auto-generated npm scripts — Injects
ios:{env},android:{env},start:{env}scripts (and, for Expo projects only,build:{env}:{platform}[:submit]andeas-update:{env}) into your project'spackage.jsonusingdotenv-cli;iosandandroidscripts automatically callrn-firebase syncto keep native config files in sync before each run - Local EAS build & OTA update commands (Expo only) —
rn-firebase buildruns a local-onlyeas build --local(with optional local submit) for a given environment, andrn-firebase eas-updatepublishes OTA updates viaeas update— both with a live header, rolling output tail, and full log file for troubleshooting. Both commands (and their generated scripts) currently only support Expo projects — seern-firebase buildfor rationale
| Requirement | Minimum | Notes |
|---|---|---|
| Node.js | >=22.5.0 |
ESM support required |
| firebase-tools | >=13 |
Must be installed globally (npm install -g firebase-tools) |
| Project type | — | ESM project ("type": "module" in package.json) |
| gcloud CLI | any | Optional but recommended. Used to check and enable Firebase services (Authentication, Firestore, Storage) during init, and to automatically provision the default Firestore database (with a selectable region, default nam5) when Firestore is selected. If not installed, service detection and database creation are skipped gracefully. Install from cloud.google.com/sdk. |
You must also be able to run firebase login interactively at least once so the CLI can authenticate with Firebase.
npm install -g @cardor/rn-firebase-clipnpm add -g @cardor/rn-firebase-cliyarn global add @cardor/rn-firebase-cliAfter installation, the rn-firebase binary is available globally:
rn-firebase --version
# 0.1.0npm install --save-dev @cardor/rn-firebase-cliThis gives you TypeScript types for the config schema (see API). Run the CLI via npx:
npx rn-firebase init# 1. Install globally
npm install -g @cardor/rn-firebase-cli
# 2. Navigate to your React Native project
cd my-react-native-app
# 3. Run the interactive setup
rn-firebase init
# 4. Check what got configured
rn-firebase statusThe wizard will guide you through:
- Choosing to use an existing Firebase project or create a new one
- Selecting or creating a Firebase project
- Enabling Firebase services (Authentication, Firestore, Storage)
- Automatically creating the default Firestore database (region selectable, default
nam5) if one doesn't already exist - Choosing platforms (Android, iOS, or both)
- Picking an environment name (dev, staging, prod, or custom)
- Downloading config files
- Wiring everything into your project
Interactive wizard to configure Firebase in your React Native project.
rn-firebase init [options]
| Flag | Type | Default | Description |
|---|---|---|---|
--project <id> |
string |
(interactive prompt) | Firebase project ID. Skips the interactive project selection. |
--platform <platform> |
"android" | "ios" | "both" |
(interactive prompt) | Platform(s) to configure. Skips the platform selection prompt. |
--out <dir> |
string |
"keys" |
Output directory for google-services.json and GoogleService-Info.plist. |
--no-gitignore |
boolean |
true (gitignore enabled) |
Skip adding the output directory to .gitignore. |
- Checks that
firebase-toolsis installed globally - Ensures you are authenticated with Firebase (triggers
firebase loginif needed) - Detects project type (Expo or Bare RN)
- Reads bundle IDs from
app.json(or prompts for them) - Prompts whether to use an existing Firebase project or create a new one
- If creating: prompts for a display name and project ID, then calls
firebase projects:create - If using existing: lists your projects and prompts you to select one
- If creating: prompts for a display name and project ID, then calls
- Shows a multi-select for Firebase services to enable (Authentication, Cloud Firestore, Cloud Storage)
- Services already enabled on the project are shown as dimmed/pre-checked and cannot be toggled
- Newly selected services are enabled via
gcloud services enable - If Firestore is selected (or already enabled) and no default database exists yet, prompts for a database location (default:
nam5) and creates it viagcloud firestore databases create - If Authentication is selected or already enabled, prints a prominent boxed reminder with a direct link to
https://console.firebase.google.com/project/<projectId>/authentication/providersto activate a sign-in provider, plus a note that you must re-download your native config files (GoogleService-Info.plist/google-services.json) afterward — theREVERSED_CLIENT_IDneeded for sign-in may only be added once a provider is enabled. Runrn-firebase updateto re-download the latest config files
- Verifies matching Firebase apps exist (by package name / bundle ID)
- Downloads
google-services.json(Android) and/orGoogleService-Info.plist(iOS) - Extracts the OAuth web client ID from
google-services.json - Writes all config files
- Generates a
.env.{envName}file with all Firebase environment variables - Updates
app.jsonwithgoogleServicesFilepaths (Expo) - Updates
.gitignoreto exclude the output directory and.env.*files - Prints a usage hint showing how to consume the env vars in your app
Note: Steps 5 and 6 (project selection and service enablement) are skipped when
--project <id>is passed — the CLI goes directly to app verification in non-interactive mode.
| File | Condition |
|---|---|
{outDir}/google-services.json |
Android or both platforms |
{outDir}/GoogleService-Info.plist |
iOS or both platforms |
config/firebase.config.{ts|mjs|js} |
Always (runtime config for your app) |
rn-firebase.config.{ts|mjs|js} |
Always (CLI config, reusable for updates) |
.env.{envName} |
Always (Firebase env vars for the selected environment) |
Check which Firebase config files are present and configured.
rn-firebase status
None.
- Loads
rn-firebase.config.*from the current directory - Checks for the existence of:
{outDir}/google-services.json{outDir}/GoogleService-Info.plistconfig/directory (forconfig/firebase.config.*)rn-firebase.configfile
- Prints green checkmarks (
✔) or red crosses (✗) for each file - Displays current configuration: environments, platform, output directory
Re-download Firebase config files. Useful after adding new apps to a project, changing project settings, or switching environments.
rn-firebase update [options]
| Flag | Type | Default | Description |
|---|---|---|---|
--env <name> |
string |
First environment in config | Target environment name (e.g. dev, staging, prod). |
- Loads
rn-firebase.config.*from the current directory - Finds the target environment (by
--envor first entry) - Checks
firebase-toolsand ensures authentication - Re-downloads
google-services.jsonand/orGoogleService-Info.plist - Re-extracts the web client ID
- Re-writes all config files (same locations as
init)
Note: You must have run rn-firebase init at least once before using update — it reads the existing rn-firebase.config.* to know what to download.
Add a new Firebase environment to an already-initialized project. Use this when you have already run rn-firebase init and want to add a second (or third) environment — for example, adding a staging or prod Firebase project after setting up dev.
rn-firebase add
No flags — the command is fully interactive.
- Loads
rn-firebase.config.*from the current directory — exits with an error if not found - Reads
platform,outDir, and bundle IDs (packageName/bundleId) from the existing config — does not re-prompt for these - Checks
firebase-toolsis installed and ensures you are authenticated - Prompts: select or create a Firebase project for the new environment
- Prompts: choose an environment name (
dev,staging,prod, or custom) - Looks up existing Firebase apps by bundle ID — offers to create them if missing
- Downloads
google-services.jsonand/orGoogleService-Info.plistfor the new environment - Merges the new environment into
rn-firebase.config.*— replaces an existing entry with the same name, otherwise appends - If
app.config.tsalready exists (Expo projects), regenerates it with all environments - Injects
package.jsonscripts for the new environment name
Example — adding a production environment after init created dev:
# Already ran: rn-firebase init (created dev env)
rn-firebase add
# Prompts: Firebase project → prod-project-id
# Prompts: env name → prod
# Result: rn-firebase.config.* now has both dev + prod envsNote: You must have run rn-firebase init at least once before using add. The bundle IDs (packageName and bundleId) are taken from the first environment in the existing config — all environments share the same app identity.
Copies the downloaded Firebase config files from the output directory (outDir) into the correct native folders expected by the build system.
rn-firebase sync
rn-firebase sync --env staging
rn-firebase sync --env staging --clean-if-changed| Flag | Description |
|---|---|
--env <name> |
Environment to sync (default: first env in config) |
--clean-if-changed |
Delete the native ios/ or android/ folder instead of copying in place, whenever the active env's Firebase config file changed since the last run (see below). Included by default in the scripts generated by init/add/update-scripts. |
- Loads
rn-firebase.config.*— exits with an error if not found - Resolves the target environment (by name or defaults to the first)
- Android (if
platformisandroidorboth):- Source:
{outDir}/{env}-{packageName}-google-services.json - Destination:
android/app/google-services.json - If
--clean-if-changedis set, see "Hash-based native folder cleaning" below — this replaces steps 4-6 whenever the hash changed - If
android/app/does not exist (prebuild not yet run) → prints a warning and skips, does not exit - SHA-256 comparison: if source and destination are identical → reports "already up to date", no write
- Otherwise: copies the file
- Source:
- iOS (if
platformisiosorboth):- Source:
{outDir}/{env}-{bundleId}-GoogleService-Info.plist - Destination: resolved by reading
app.json → expo.name → ios/{name}/first, then falling back to scanningios/for the first subdirectory that already containsGoogleService-Info.plist - If
--clean-if-changedis set, the hash check happens before this destination resolution — see below - If no native iOS folder is found → prints a warning and skips
- SHA-256 comparison: if already in sync → reports "already up to date"
- Otherwise: copies the file
- Source:
- No network calls, no firebase-tools, no authentication required
Expo only copies googleServicesFile into the native ios//android/ projects during expo prebuild — it is not re-copied on every expo run:*. So if you switch APP_ENV (dev/staging/prod) after the native folders already exist, the stale config silently stays in place unless you remember to run a clean prebuild yourself.
--clean-if-changed automates that: for each active platform, it hashes (SHA-256) the resolved source config file for the active env (never the destination — the destination may not exist yet) and compares it against the hash stored the last time sync ran, in a local, gitignored state file at .rn-firebase/env-state.json (shape: { "android"?: string, "ios"?: string }, one hash per platform).
- If the hash differs from what's stored (or nothing is stored yet, e.g. first run) → the corresponding native folder (
ios/orandroid/) is deleted entirely and the new hash is persisted. The in-place file copy is skipped for that platform since the folder no longer exists — runexpo prebuildafterwards to regenerate it with the correct config baked in. - If the hash matches → nothing is deleted;
syncfalls back to its normal in-place copy/"already up to date" behavior for that platform, untouched.
Android and iOS are tracked and cleaned independently — changing only the iOS config will never touch android/, and vice versa. Comparing file content (not just env name) also means dev/local sharing the same underlying config file won't trigger unnecessary cleanup.
The .rn-firebase/ directory is added to .gitignore automatically (best-effort) by init and by sync itself the first time it writes the state file.
Tip: The
ios:{env}andandroid:{env}scripts injected byinit/update-scriptsautomatically callrn-firebase sync --clean-if-changedbefore launching the app.
Updates the ios:{env}, android:{env} scripts (and, for Expo projects only, build:{env}:{platform}[:submit] and eas-update:{env}) in your package.json.
rn-firebase update-scriptsNo flags.
- Loads
rn-firebase.config.*— exits with an error if not found - Detects the project type (
rn-firebase detectProjectType) — Expo, bare React Native, or undetected - Reads
package.jsonfrom the current directory - For each environment in
config.envs, builds the expected script values (only for the platforms enabled inconfig.platform):ios:{env}→rn-firebase sync --env {env} --clean-if-changed && APP_ENV={env} dotenv -e .env.{env} -- expo run:iosandroid:{env}→rn-firebase sync --env {env} --clean-if-changed && APP_ENV={env} dotenv -e .env.{env} -- expo run:android--clean-if-changedis included by default — see Hash-based native folder cleaning for what it does- Expo projects only:
build:{env}:ios→rn-firebase build --platform ios --env {env} --profile productionbuild:{env}:ios:submit→rn-firebase build --platform ios --env {env} --profile production --submitbuild:{env}:android→rn-firebase build --platform android --env {env} --profile productionbuild:{env}:android:submit→rn-firebase build --platform android --env {env} --profile production --submiteas-update:{env}→rn-firebase eas-update --profile {env}
- If a script already exists and starts with the corresponding
rn-firebase sync/rn-firebase build/rn-firebase eas-updateprefix → skips it (already up to date) - Otherwise → overwrites with the new value (adds the prefix to old/custom scripts, or creates new ones)
- Writes
package.jsonback and prints a summary. For bare React Native (or undetected) projects, also prints an informational line explaining thatbuild:/eas-update:scripts were skipped because they are an Expo-only feature.
Expo-only scripts: the
build:{env}:{platform}[:submit]andeas-update:{env}scripts are only generated for Expo projects (detected viaapp.json'sexpokey orapp.config.js/ts). Bare React Native projects (and projects where the type can't be detected) still getios:{env}/android:{env}scripts — seern-firebase buildfor why build/eas-update support is Expo-only for now.
Migration note: If you already ran
rn-firebase initon an Expo project, runrn-firebase update-scriptsonce to update your existing scripts to include the automatic sync step, and to add the newbuild:{env}:{platform}andeas-update:{env}scripts.
eas-updatemessage flag: The generatedeas-update:{env}script deliberately omits-m/--message, since the update message varies for every publish. Pass it through with npm/pnpm's argument-forwarding syntax:npm run eas-update:dev -- -m "your message" # or pnpm eas-update:dev -m "your message"
Runs a local-only EAS build (eas build --local) for a given environment, with an optional local submit step. This command never runs a remote/cloud EAS build — for remote builds, use the eas CLI directly.
Expo only:
rn-firebase buildcurrently only supports Expo projects (EAS tooling is Expo's build/submit toolchain — there is no equivalent bare React Native path yet). Running it in a bare React Native project (or a project whose type can't be detected) prints an error and exits before any config loading oreasinvocation. Bare RN build support is planned for a future release.
rn-firebase build
rn-firebase build --platform android --env staging
rn-firebase build --platform ios --profile preview --submit
rn-firebase build --platform all --binary-version latest --submit| Flag | Description | Default |
|---|---|---|
-p, --platform <platform> |
Platform to build for: android, ios, or all |
all |
--env <name> |
Environment name, validated against rn-firebase.config's envs[] |
first env in config |
--profile <profile> |
EAS build profile (free-form — not restricted to a fixed list; passed through to eas as-is) |
production |
-o, --output <dir> |
Base output folder for local build artifacts (a per-platform subfolder + versioned filename is appended automatically — see below) | build |
--submit |
Also run eas submit --local after a successful build |
off |
--binary-version <version|latest> |
Reuse an existing binary (latest, or a specific build id) instead of running a local build — skips the build step entirely |
none |
-s, --skip-build-validation |
Skip the built-version duplicate check | off |
- Detects the project type; if it's bare React Native (or undetected), prints an Expo-only error and exits before loading
rn-firebase.config.*or doing anything else. - Loads
rn-firebase.config.*and validates--envagainstconfig.envs[].name(same validation asrn-firebase sync/update) — exits with an error if the config or environment is not found. - If a consumer
eas.jsonexists and--profileisn't one of itsbuildprofile keys, prints a non-blocking warning and continues (the profile is never validated as a hard-coded enum). - Performs an advisory, non-blocking duplicate-build check: resolves the app version from
app.json'sexpo.version, falling back topackage.json'sversion, and warns (without stopping) if that version + platform combination was already built before, based on a local dedup file at.rn-firebase/built-versions.json(gitignored, no git operations of any kind). Skip this check entirely with--skip-build-validation. - Loads
.env.<envName>(viadotenv) and setsprocess.env.APP_ENVto the environment name before invokingeas. - Resolves a concrete, versioned artifact path for each platform to build, scoped under its own subfolder of
<dir>soiosandandroidnever write to the same location:<dir>/<platform>/<appName>-<version>-<profile>-<envName>.<ipa|aab>(.ipaforios,.aabforandroid).<appName>comes from your project'spackage.jsonnamefield (falls back toappif missing/unreadable);<version>comes from the sameapp.json/package.jsonresolution used by the duplicate-build check in step 4 (falls back tounversionedif neither resolves). When--platform allis passed, this runs independently foriosthenandroid, each with its own resolved path — this also fixes a bug where both platforms previously shared one bare output directory and could clobber each other's artifacts. - Runs
eas build --local --platform <platform> --profile <profile> --output <resolved-path> --non-interactivefor each resolved platform, showing a persistent header (platform, profile, env + which.env.<name>file was loaded, "local build" indicator) above a rolling tail of the build output. - Mirrors the full combined build output to a log file under your OS temp directory (
rn-firebase-build-<timestamp>.log) and prints its path when the command finishes (success or failure). - On failure, prints the exact failed command plus the last 10 lines of output, and exits non-zero — this stops the whole command immediately, including any remaining platform when
--platform allis used (no partial-success continuation). - On success, records the built version for the duplicate check (step 4) — tracked independently per platform — and, if
--submitwas passed, runseas submit --local --path <resolved-path>using the exact same resolved artifact path as the build step (or, when--binary-versionis set,eas submit --latest/--id <version>against the existing binary, with no local artifact path involved) with the same header/tail/log-file behavior.
Local-only by design:
rn-firebase buildintentionally has no--local/--remotetoggle — it only ever runs local EAS builds. If you need a remote/cloud EAS build, run theeasCLI directly (e.g.eas build --platform android --profile production).
Publishes an OTA update via eas update. This is a distinct command from rn-firebase update (which re-downloads Firebase config files) — eas-update only talks to EAS Update.
Expo only: like
rn-firebase build,rn-firebase eas-updatecurrently only supports Expo projects (EAS Update is Expo's OTA update service — there is no bare React Native equivalent). Running it in a bare React Native project (or a project whose type can't be detected) prints an error and exits immediately.
rn-firebase eas-update --profile production --message "fix: crash on launch"
rn-firebase eas-update --profile preview -m "test update"| Flag | Description | Default |
|---|---|---|
--profile <profile> |
EAS update profile (free-form — also used as the update branch name) | production |
-m, --message <text> |
Update message (required — the command exits with an error if omitted) | — |
- Detects the project type; if it's bare React Native (or undetected), prints an Expo-only error and exits immediately.
- Requires
-m/--message; exits with a clear error if it's missing. - If a consumer
eas.jsonexists and--profileisn't one of itsbuildprofile keys, prints a non-blocking warning and continues. - Maps the update branch to the
--profilevalue (there is no separate--branchflag in this command — the profile name is used as the branch). - Runs
eas update --branch <profile> --message "<text>" --non-interactive, with the same persistent header, rolling tail, tmpdir log file, and failure-tail behavior described forrn-firebase build.
Local-only by design: like
rn-firebase build,rn-firebase eas-updateis a thin, opinionated wrapper — for anything beyond this (custom branch names, rollout percentages, etc.), use theeasCLI directly.
The CLI generates two config files during init. Both are auto-generated — do not edit them directly. Re-run rn-firebase init or rn-firebase update to make changes.
This is the CLI configuration file. It tells rn-firebase what to download and where to put it. The CLI uses it for the status and update commands.
The file extension depends on your project:
.ts— iftsconfig.jsonexists.mjs— ifpackage.jsonhas"type": "module".js— ifpackage.jsonhas"type": "commonjs"or notypefield (default for Expo/RN projects)
// rn-firebase.config.ts
import type { RNFConfig } from '@cardor/rn-firebase-cli'
export default {
platform: 'both',
outDir: 'keys',
envs: [
{
name: 'dev',
googleCloudProjectId: 'my-project-dev',
firebaseProjectId: 'my-project-dev',
android: { packageName: 'com.myapp.dev' },
ios: { bundleId: 'com.myapp.dev' },
},
],
} satisfies RNFConfig| Field | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Environment name (e.g. dev, staging, prod) |
googleCloudProjectId |
string |
Yes | GCP project ID |
firebaseProjectId |
string |
No | Firebase project ID (defaults to googleCloudProjectId) |
android.packageName |
string |
No | Android package name |
ios.bundleId |
string |
No | iOS bundle identifier |
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
platform |
"android" | "ios" | "both" |
Yes | — | Configured platforms |
outDir |
string |
No | "keys" |
Output directory for downloaded config files |
envs |
FirebaseEnv[] |
Yes | — | Environment configurations |
This is a runtime config file for your application code. It contains static values (package names, bundle IDs) and references the Firebase web client ID via an environment variable rather than hardcoding it:
// config/firebase.config.ts
export const FIREBASE_CONFIG = {
webClientId: process.env.EXPO_PUBLIC_FIREBASE_WEB_CLIENT_ID,
androidPackageName: 'com.myapp.dev',
iosBundleId: 'com.myapp.dev',
}Use it in your app like this:
import { FIREBASE_CONFIG } from '../config/firebase.config'During init, the CLI generates a .env.{envName} file (e.g. .env.dev, .env.staging, .env.prod) in your project root. It contains all Firebase environment variables extracted from google-services.json.
The variable names include a prefix that matches your project type:
Expo (EXPO_PUBLIC_ prefix — required for Expo's client-side env var system):
EXPO_PUBLIC_FIREBASE_API_KEY=AIzaSy...
EXPO_PUBLIC_FIREBASE_AUTH_DOMAIN=my-project.firebaseapp.com
EXPO_PUBLIC_FIREBASE_PROJECT_ID=my-project-dev
EXPO_PUBLIC_FIREBASE_STORAGE_BUCKET=my-project-dev.appspot.com
EXPO_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=123456789
EXPO_PUBLIC_FIREBASE_APP_ID=1:123456789:android:abcdef
EXPO_PUBLIC_FIREBASE_WEB_CLIENT_ID=123456789-xxxxx.apps.googleusercontent.com
Bare React Native (no prefix — react-native-config convention):
FIREBASE_API_KEY=AIzaSy...
FIREBASE_AUTH_DOMAIN=my-project.firebaseapp.com
FIREBASE_PROJECT_ID=my-project-dev
FIREBASE_STORAGE_BUCKET=my-project-dev.appspot.com
FIREBASE_MESSAGING_SENDER_ID=123456789
FIREBASE_APP_ID=1:123456789:android:abcdef
FIREBASE_WEB_CLIENT_ID=123456789-xxxxx.apps.googleusercontent.com
If the env file already exists with different content, the CLI will prompt before overwriting. The .env.* pattern is automatically added to .gitignore so these files are not committed.
Expo: Variables are available as process.env.EXPO_PUBLIC_FIREBASE_* anywhere in your app code.
const apiKey = process.env.EXPO_PUBLIC_FIREBASE_API_KEY
const webClientId = process.env.EXPO_PUBLIC_FIREBASE_WEB_CLIENT_IDBare React Native: Install react-native-config and access vars via Config.*:
npm install react-native-configimport Config from 'react-native-config'
const apiKey = Config.FIREBASE_API_KEY
const webClientId = Config.FIREBASE_WEB_CLIENT_IDEvery time you run rn-firebase init, the CLI sets up one environment (e.g. dev, staging, or prod). When you run it a second time for a different environment, it writes a second set of Firebase config files alongside the first — using a prefixed naming scheme to keep them separate.
Firebase native config files are written with the pattern {env}-{id}-{base}:
| Platform | Pattern | Example |
|---|---|---|
| Android | {env}-{packageName}-google-services.json |
dev-com.myapp-google-services.json |
| iOS | {env}-{bundleId}-GoogleService-Info.plist |
prod-com.myapp-GoogleService-Info.plist |
This means your keys/ directory will look like:
keys/
├── dev-com.myapp-google-services.json
├── dev-com.myapp-GoogleService-Info.plist
├── prod-com.myapp-google-services.json
└── prod-com.myapp-GoogleService-Info.plist
Expo reads whichever path is currently set in app.json (or app.config.*) — switching an environment variable alone is not enough. To switch between environments at build time, use app.config.ts with dynamic paths driven by an APP_ENV environment variable.
When you run rn-firebase init for a second environment and the CLI detects that app.json already has a googleServicesFile set (from the first run), it will offer to auto-generate an app.config.ts:
Multiple envs detected. Generate app.config.ts for dynamic Firebase paths (APP_ENV)?
If you accept, the CLI writes an app.config.ts that maps each environment to its correct Firebase config file:
// Auto-generated by @cardor/rn-firebase-cli
// Commit this file. Do NOT commit your .env.* files.
import type { ExpoConfig } from 'expo/config'
import appJsonData from './app.json'
import { dirname, resolve } from 'path'
import { fileURLToPath } from 'url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const env = (process.env.APP_ENV ?? 'dev') as string
const ENV_COLORS: Record<string, string> = {
dev: '\x1b[36m',
staging: '\x1b[33m',
prod: '\x1b[31m',
}
const envColor = ENV_COLORS[env] ?? '\x1b[35m'
console.log(`${envColor}[rn-firebase-cli] Active environment: ${env}\x1b[0m`)
const firebaseFiles: Record<string, { android?: string; ios?: string }> = {
dev: {
android: resolve(__dirname, 'keys/dev-com.myapp-google-services.json'),
ios: resolve(__dirname, 'keys/dev-com.myapp-GoogleService-Info.plist'),
},
prod: {
android: resolve(__dirname, 'keys/prod-com.myapp-google-services.json'),
ios: resolve(__dirname, 'keys/prod-com.myapp-GoogleService-Info.plist'),
},
}
const config = {
...appJsonData.expo,
android: {
...(appJsonData.expo?.android as object | undefined),
googleServicesFile: firebaseFiles[env]?.android,
},
ios: {
...(appJsonData.expo?.ios as object | undefined),
googleServicesFile: firebaseFiles[env]?.ios,
},
} as ExpoConfig
export default configThen set APP_ENV before running your build:
APP_ENV=prod npx expo build
APP_ENV=dev npx expo startColored environment indicator: the generated app.config.ts prints a color-coded line to the console showing which APP_ENV is active whenever Expo evaluates it (e.g. on expo start, expo prebuild, or eas build):
[rn-firebase-cli] Active environment: dev
This uses plain ANSI escape codes (no extra dependency required in your project — chalk is only used internally by the CLI itself, never in generated output). Color mapping: dev is cyan, staging is yellow, prod is red, and any custom/unrecognized environment name falls back to magenta. This helps catch accidental builds against the wrong environment at a glance.
If app.config.ts already exists (you created it manually), the CLI will print the snippet to add to your firebaseFiles map instead of overwriting.
Expo SDK 49 and later load .env files automatically — no extra setup needed. Set APP_ENV via your build tool or shell.
Expo SDK 48 and earlier require dotenv/config to be loaded manually. Add this to the top of your app.config.ts:
import 'dotenv/config'After running init or update, the CLI automatically injects convenience scripts into your project's package.json based on your platform and environment name.
dotenv-cli must be installed in your project:
pnpm add -D dotenv-cliIf it is missing, the CLI prints a warning but still writes the scripts.
Expo-only scripts:
build:{env}:{platform}[:submit]andeas-update:{env}are only injected for Expo projects. Bare React Native projects (and projects whose type can't be detected) still getios:{env}/android:{env}/start:{env}.
For platform: 'ios' and envName: 'dev' (Expo project):
{
"scripts": {
"ios:dev": "rn-firebase sync --env dev --clean-if-changed && APP_ENV=dev dotenv -e .env.dev -- expo run:ios",
"start:dev": "APP_ENV=dev dotenv -e .env.dev -- expo start",
"build:dev:ios": "rn-firebase build --platform ios --env dev --profile production",
"build:dev:ios:submit": "rn-firebase build --platform ios --env dev --profile production --submit",
"eas-update:dev": "rn-firebase eas-update --profile dev"
}
}For platform: 'android' (Expo project):
{
"scripts": {
"android:dev": "rn-firebase sync --env dev --clean-if-changed && APP_ENV=dev dotenv -e .env.dev -- expo run:android",
"start:dev": "APP_ENV=dev dotenv -e .env.dev -- expo start",
"build:dev:android": "rn-firebase build --platform android --env dev --profile production",
"build:dev:android:submit": "rn-firebase build --platform android --env dev --profile production --submit",
"eas-update:dev": "rn-firebase eas-update --profile dev"
}
}For platform: 'both', all of the above are injected: ios:dev, android:dev, start:dev, build:dev:ios, build:dev:ios:submit, build:dev:android, build:dev:android:submit, and eas-update:dev.
The --clean-if-changed flag is included by default — it deletes the native ios//android/ folder instead of copying in place whenever the active env's Firebase config file changed since the last run, forcing a clean expo prebuild. See Hash-based native folder cleaning for details.
The generated eas-update:{env} script deliberately omits -m/--message (the update message varies per publish). Forward it via npm run eas-update:dev -- -m "your message" (or pnpm eas-update:dev -m "your message").
Scripts that already exist in package.json are never overwritten. To update existing scripts with the sync/build/eas-update prefix, run rn-firebase update-scripts.
cd my-react-native-app
rn-firebase initrn-firebase init \
--project my-firebase-project \
--platform both \
--out firebase-keysrn-firebase init --platform androidrn-firebase init --no-gitignorern-firebase statusExample output:
Firebase setup status
✔ google-services.json
✔ GoogleService-Info.plist
✔ config/firebase.config.*
✔ rn-firebase.config
Environments configured: dev, staging
Platform: both
Output dir: keys
rn-firebase updatern-firebase update --env stagingmy-react-native-app/
├── rn-firebase.config.ts # CLI config (auto-generated)
├── .env.dev # Firebase env vars — dev (gitignored)
├── config/
│ └── firebase.config.ts # Runtime config (auto-generated)
├── keys/ # Output directory (gitignored)
│ ├── google-services.json # Android Firebase config (auto-generated)
│ └── GoogleService-Info.plist # iOS Firebase config (auto-generated)
└── ...rest of your project
@cardor/rn-firebase-cli/
├── bin/
│ └── rfc.js # CLI entry point (#!/usr/bin/env node)
├── src/
│ ├── cli.ts # Commander setup, 3 commands
│ ├── index.ts # Public API (type exports only)
│ ├── types.ts # All type definitions
│ ├── commands/
│ │ ├── init.ts # runInit() — full setup wizard (285 lines)
│ │ ├── status.ts # runStatus() — check config presence (42 lines)
│ │ └── update.ts # runUpdate() — re-download configs (83 lines)
│ ├── core/
│ │ ├── config/
│ │ │ ├── load.ts # Config loader
│ │ │ ├── defaults.ts # Default values
│ │ │ └── templates.ts # File content generators
│ │ ├── materializer/
│ │ │ ├── index.ts # RNMaterializer interface + factory
│ │ │ ├── expo.ts # ExpoMaterializer (fully implemented)
│ │ │ └── bare-rn.ts # BareRNMaterializer (placeholder)
│ │ ├── firebase/
│ │ │ ├── auth.ts # firebase-tools check & login
│ │ │ ├── projects.ts # List Firebase projects
│ │ │ ├── apps.ts # List Android/iOS apps
│ │ │ ├── config-download.ts# Download google-services & GoogleService-Info
│ │ │ └── web-client.ts # Extract web OAuth client ID
│ │ └── detector/
│ │ ├── index.ts # Project type detection
│ │ ├── config-ext.ts # Config extension detection (ts/mjs/js)
│ │ └── bundle-ids.ts # Bundle ID detection from app.json / native files
│ ├── utils/
│ │ └── envFile.ts # Env file generation utilities
│ └── tests/
│ ├── config.test.ts # Tests for defaults, templates, web client
│ ├── detector.test.ts # Tests for project type & config extension
│ ├── materializer.test.ts # Tests for materializers
│ └── envFile.test.ts # Tests for env file utilities
├── dist/ # Build output (compiled)
├── tsup.config.ts # Build config (ESM, ES2022)
├── tsconfig.json
└── package.json
The package exports TypeScript types only (no runtime code). Import them to type-check your configuration files.
import type {
ConfigExt, // 'ts' | 'mjs' | 'cjs' | 'js'
FirebaseApp, // { appId, displayName?, packageName?, bundleId? }
FirebaseEnv, // { name, googleCloudProjectId, firebaseProjectId?, android?, ios? }
FirebaseProject, // { projectId, displayName }
MaterializeParams, // Parameters for the materializer build method
Platform, // 'android' | 'ios' | 'both'
ProjectType, // 'expo' | 'bare'
RNFConfig, // { platform, outDir, envs }
} from '@cardor/rn-firebase-cli'Use them in your rn-firebase.config.ts:
import type { RNFConfig } from '@cardor/rn-firebase-cli'
export default {
platform: 'both',
outDir: 'keys',
envs: [
{
name: 'prod',
googleCloudProjectId: 'my-project-prod',
android: { packageName: 'com.myapp' },
},
],
} satisfies RNFConfiggit clone https://github.com/enmanuelmag/rn-firebase-cli.git
cd rn-firebase-cli
pnpm install| Script | Command | Description |
|---|---|---|
pnpm dev |
tsx src/cli.ts |
Run the CLI without building (uses tsx for TS execution) |
pnpm build |
tsup && pnpm typecheck |
Build to dist/ with TypeScript declarations |
pnpm test |
node --test --import tsx/esm src/tests/*.test.ts |
Run test suite (30+ tests) |
pnpm typecheck |
tsc --noEmit |
Type-check without emitting files |
pnpm lint |
eslint . --fix |
Lint and auto-fix |
pnpm format |
prettier --write src scripts |
Format source files |
pnpm dev init # equivalent to: rn-firebase init
pnpm dev status # equivalent to: rn-firebase status
pnpm dev update # equivalent to: rn-firebase updatepnpm testThe test suite uses Node's built-in test runner (node:test) with tsx for TypeScript transpilation. Tests cover:
- Config defaults and templates
- Web client ID extraction from
google-services.json - Project type detection
- Config extension detection
pnpm buildUses tsup to bundle two entry points:
cli(src/cli.ts) — the CLI binaryindex(src/index.ts) — the type-only public API
Output goes to dist/ as ESM modules targeting ES2022.
| Area | Status |
|---|---|
| Expo (managed & bare) | Fully supported. Auto-detects project type, reads app.json, writes googleServicesFile fields, generates config files. |
| Bare React Native | Placeholder — coming in v2. The CLI detects Bare RN projects and runs through the interactive wizard, but file creation is stubbed with informational messages. You can still select a Firebase project and download config files, but native build file integration is not yet implemented. |
| Dynamic app.config | Projects using app.config.js or app.config.ts (instead of app.json) are detected, but bundle IDs must be entered manually and googleServicesFile fields must be added by hand. |
build/eas-update (Bare RN) |
Expo-only for now. rn-firebase build and rn-firebase eas-update rely on Expo's EAS build/update tooling, which has no bare React Native equivalent. Both commands exit with an error on bare RN (or undetected) projects, and the corresponding build:{env}:{platform}[:submit]/eas-update:{env} scripts are omitted by rn-firebase init/update-scripts for those projects. |
Apache 2.0 © Enmanuel Magallanes
Created and maintained by Enmanuel Magallanes.
@cardor/rn-firebase-cli — Automated Firebase setup for React Native projects.