@cardor/rn-firebase-cli
v0.4.2
Published
CLI interactivo para setup automatizado de Firebase en proyectos React Native (Expo y Bare)
Maintainers
Readme
@cardor/rn-firebase-cli
Automated Firebase setup for React Native (Expo & Bare)
Table of Contents
- @cardor/rn-firebase-cli
What is this?
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.
Features
- 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
Prerequisites
| 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.
Installation
Global install (recommended for CLI usage)
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.0Local install (for programmatic types)
npm 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 initQuick Start
# 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
CLI Commands Reference
rn-firebase init
Interactive wizard to configure Firebase in your React Native project.
rn-firebase init [options]Flags
| 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. |
What it does
- 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.
Files created
| 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) |
rn-firebase status
Check which Firebase config files are present and configured.
rn-firebase statusFlags
None.
What it does
- 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
rn-firebase update
Re-download Firebase config files. Useful after adding new apps to a project, changing project settings, or switching environments.
rn-firebase update [options]Flags
| Flag | Type | Default | Description |
| -------------- | -------- | --------------------------- | -------------------------------------------------------- |
| --env <name> | string | First environment in config | Target environment name (e.g. dev, staging, prod). |
What it does
- 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.
rn-firebase add
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 addNo flags — the command is fully interactive.
What it does
- 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.
rn-firebase sync
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-changedFlags
| 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. |
What it does
- 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
Hash-based native folder cleaning (--clean-if-changed)
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.
rn-firebase update-scripts
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.
What it does
- 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"
rn-firebase build
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 --submitFlags
| 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 |
What it does
- 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).
rn-firebase eas-update
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"Flags
| 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) | — |
What it does
- 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.
Configuration
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.
rn-firebase.config.*
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 RNFConfigEnvironment (FirebaseEnv)
| 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 |
Root config (RNFConfig)
| 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 |
config/firebase.config.*
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'.env.{envName}
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.comBare 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.comIf 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.
Accessing env vars in your app
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_IDMulti-Environment Setup
Overview
Every 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.
Prefixed file naming
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.plistAPP_ENV pattern and app.config.ts
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: devThis 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.
Loading .env files in Expo
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'Auto-generated package.json scripts
After running init or update, the CLI automatically injects convenience scripts into your project's package.json based on your platform and environment name.
Requirement
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.
Generated 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.
Usage Examples
Interactive setup (full wizard)
cd my-react-native-app
rn-firebase initNon-interactive setup with all flags
rn-firebase init \
--project my-firebase-project \
--platform both \
--out firebase-keysAndroid-only configuration
rn-firebase init --platform androidSkip .gitignore update
rn-firebase init --no-gitignoreCheck configuration status
rn-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: keysUpdate config files (re-download)
rn-firebase updateUpdate a specific environment
rn-firebase update --env stagingProject Structure
my-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 projectPackage structure
@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.jsonAPI
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 RNFConfigDevelopment
Setup
git clone https://github.com/enmanuelmag/rn-firebase-cli.git
cd rn-firebase-cli
pnpm installScripts
| 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 |
Running in development mode
pnpm dev init # equivalent to: rn-firebase init
pnpm dev status # equivalent to: rn-firebase status
pnpm dev update # equivalent to: rn-firebase updateTesting
pnpm 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
Building
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.
Limitations
| 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. |
License
Apache 2.0 © Enmanuel Magallanes
About
Created and maintained by Enmanuel Magallanes.
@cardor/rn-firebase-cli — Automated Firebase setup for React Native projects.
