DEV Community

Cover image for One CLI to Rule Them All, One Command to Configure

One CLI to Rule Them All, One Command to Configure

The setup tax

Every Firebase + React Native project starts the same way. You open the Firebase Console. You create an Android app with your package name. You create an iOS app with your bundle ID. You download google-services.json. You download GoogleService-Info.plist. You open app.json and add the googleServicesFile paths. You go back to your terminal, extract the API keys, write a .env.dev file. You add the output directory and .env.* to .gitignore.

Then your team lead says "we need a staging environment."

You do it again.

Website: https://stack.cardor.dev/rfc

Then QA finds a bug that only reproduces in prod, and someone — not you, obviously — had google-services.json from dev pointing to the prod Firebase project because filenames were identical and got overwritten during the last setup. Nobody knew. The workflow "worked."

This is the setup tax. It's not technically hard. It's just repetitive, error-prone, and completely uninteresting to solve manually.

What the CLI does

@cardor/rn-firebase-cli is a Node.js CLI that wraps the entire Firebase setup flow for React Native projects. One command. Interactive wizard. Everything wired automatically.

npm install -g @cardor/rn-firebase-cli
cd my-react-native-app
rn-firebase init
Enter fullscreen mode Exit fullscreen mode

The wizard:

  1. Checks that firebase-tools is installed and you're authenticated
  2. Detects your project type (Expo or Bare RN) by scanning app.json, app.config.*, android/, ios/
  3. Reads your bundle IDs from app.json — or prompts for them if they're not found
  4. Lists your Firebase projects and lets you pick one
  5. Verifies matching Android/iOS apps exist by package name and bundle ID
  6. If no matching app exists: prompts you to create it right there via firebase apps:create — no Console tab-switching required
  7. Downloads google-services.json and GoogleService-Info.plist
  8. Extracts the OAuth web client ID automatically
  9. Writes config/firebase.config.ts (or .mjs/.cjs depending on your project)
  10. Writes .env.{envName} with all Firebase vars, prefixed correctly for your project type (EXPO_PUBLIC_ for Expo, no prefix for Bare RN with react-native-config)
  11. Updates app.json with googleServicesFile paths
  12. Updates .gitignore

The multi-environment problem — solved properly

The naive approach to multiple Firebase environments is to keep overwriting the same google-services.json file. That's how you end up with prod pointing at dev. The CLI solves this at the filesystem level with prefixed filenames:

keys/
  dev-com.myapp-google-services.json
  staging-com.myapp-google-services.json
  prod-com.myapp-google-services.json
  dev-com.myapp-GoogleService-Info.plist
  prod-com.myapp-GoogleService-Info.plist
Enter fullscreen mode Exit fullscreen mode

The naming pattern is {env}-{packageName|bundleId}-{original filename}. It's deterministic, readable, and collision-proof. app.json gets updated with the correct prefixed path on each run.

The app.json problem

app.json is static JSON. It can only hold one value for googleServicesFile. That's fine for one environment. For multiple, you need app.config.ts.

When the CLI detects a second environment being added (it checks whether app.json already has a googleServicesFile), it offers:

Multiple envs detected. Generate app.config.ts for dynamic Firebase paths (APP_ENV)?
Enter fullscreen mode Exit fullscreen mode

If you accept, it generates a typed app.config.ts:

// Auto-generated by @cardor/rn-firebase-cli
import type { ExpoConfig } from 'expo/config'
import appJsonData from './app.json'

const env = (process.env.APP_ENV ?? 'dev') as string

const firebaseFiles: Record<string, { android?: string; ios?: string }> = {
  dev: {
    android: './keys/dev-com.myapp-google-services.json',
    ios: './keys/dev-com.myapp-GoogleService-Info.plist',
  },
  prod: {
    android: './keys/prod-com.myapp-google-services.json',
    ios: './keys/prod-com.myapp-GoogleService-Info.plist',
  },
}

const config: ExpoConfig = {
  ...appJsonData.expo,
  android: {
    ...(appJsonData.expo?.android as object | undefined),
    googleServicesFile: firebaseFiles[env]?.android,
  },
  ios: {
    ...(appJsonData.expo?.ios as object | undefined),
    googleServicesFile: firebaseFiles[env]?.ios,
  },
}

export default config
Enter fullscreen mode Exit fullscreen mode

It also cleans googleServicesFile out of app.json — that field is now owned by app.config.ts and the dead entry in app.json would only cause confusion.

If you already have your own app.config.ts, the CLI skips generation and prints a contextual two-step snippet showing exactly what to add and where.

Expo SDK 56 + src/ detection

Expo SDK 55+ generates a src/ directory by default. The CLI detects its presence and places firebase.config.ts inside src/config/ rather than root config/. No config needed — it's automatic.

Creating missing Firebase apps inline

Previously, if you ran the CLI against a Firebase project that didn't have an Android app with your package name, it would error and tell you to go create it in the Console. Now it asks:

No Android app found with package name "com.myapp" in project "my-project-dev".
Create Android app now? (Y/n)
Enter fullscreen mode Exit fullscreen mode

If you confirm, it calls firebase apps:create ANDROID and continues the flow. No Console tab required.

Using the configured files

After init, your app gets:

  • keys/{env}-{id}-google-services.json — native Firebase config (gitignored)
  • keys/{env}-{id}-GoogleService-Info.plist — native Firebase config (gitignored)
  • config/firebase.config.ts — runtime config with web client ID env ref
  • rn-firebase.config.ts — CLI config for future update runs
  • .env.{envName} — all Firebase vars (gitignored)

In your Expo app:

const apiKey = process.env.EXPO_PUBLIC_FIREBASE_API_KEY
const webClientId = process.env.EXPO_PUBLIC_FIREBASE_WEB_CLIENT_ID
Enter fullscreen mode Exit fullscreen mode

For builds that switch environments:

APP_ENV=prod eas build --platform all
APP_ENV=dev npx expo start
Enter fullscreen mode Exit fullscreen mode

Commands

rn-firebase init              # interactive setup
rn-firebase init \
  --project my-project \
  --platform both             # non-interactive (CI/CD)
rn-firebase update            # re-download config files
rn-firebase update --env prod # re-download specific env
rn-firebase status            # check what's configured
Enter fullscreen mode Exit fullscreen mode

Current limitations

Expo is fully supported. Bare React Native is detected and the wizard runs, but native file integration (editing android/build.gradle, ios/Podfile) is coming in v2. For now, Bare RN users get the .env file and the wizard flow.


npm install -g @cardor/rn-firebase-cli
Enter fullscreen mode Exit fullscreen mode

npmjs.com/package/@cardor/rn-firebase-cli

github.com/enmanuelmag/rn-firebase-cli

Top comments (0)