Back to blog
React NativeMobile DevelopmentDevOpsAndroidAndroid FlavorsProduct FlavorsBuild VariantsiOSiOS SchemesXcode Schemesreact-native-configMulti-EnvironmentEnvironment VariablesReact Native CLI

Multi-Environment Setup for React Native CLI

A production-ready guide to configuring Dev, Staging, and Production environments in React Native — with Android product flavors, iOS schemes, and react-native-config.

FK

Faisal Khawaj

Author

8 min read

Switching between development, staging, and production backends should not require hand-editing .env files and rebuilding from scratch. This guide walks through a battle-tested setup for React Native CLI projects: separate environment files, native build variants, and yarn scripts that keep Metro and native builds in sync.

What you'll build

EnvironmentEnv fileApp display nameAndroid flavoriOS scheme
Development.envMyApp DevdevApp-Dev
Staging.env.stagingMyApp StagingstagingApp-Staging
Production.env.productionMyAppprodApp-Prod

Each environment gets its own API URLs, feature flags, and home-screen label — from a single codebase.

The problem this solves

Without flavors or schemes, every environment switch means:

  1. Manually editing .env
  2. Rebuilding the native app
  3. Hoping Metro picked up the same values

With this setup you can:

  • Point each build at a different backend (dev / staging / prod)
  • Show a distinct app name on the home screen (e.g. "MyApp Staging")
  • Ship one codebase with separate store release configurations
  • Run QA and production builds side by side (with optional bundle ID suffixes)

Architecture overview

Code
┌─────────────┐     ┌──────────────────┐     ┌─────────────────┐
│  .env file  │────▶│  react-native-   │────▶│  src/config/    │
│  (per env)  │     │  config          │     │  env.ts         │
└─────────────┘     └──────────────────┘     └─────────────────┘
       │                     │
       ▼                     ▼
┌─────────────┐     ┌──────────────────┐
│ Metro       │     │ Native build     │
│ (JS bundle) │     │ (Android/iOS)    │
└─────────────┘     └──────────────────┘

Golden rule: Metro and the native app must load the same environment file.

✅ Correct❌ Wrong
start:staging + android:stagingstart:dev + android:staging

Quick start

1. Create environment files

Add these to your project root. They are gitignored — never commit secrets.

FileAPP_ENV valueUsed for
.envdevelopmentLocal development
.env.stagingstagingQA / staging server
.env.productionproductionApp Store / Play Store

Example .env:

Terminal
APP_ENV=development
API_URL=https://api-dev.example.com/
SOCKET_URL=https://api-dev.example.com/chat

# Analytics, payments, etc.
SENTRY_DSN=
POSTHOG_API_KEY=

Important: Environment values are baked in at build time, not read live. After changing any .env file, rebuild the native app.

2. Run with matching scripts

Open two terminals and pick one environment:

EnvironmentTerminal 1 (Metro)Terminal 2 (run app)
Devyarn start:devyarn android:dev or yarn ios:dev
Stagingyarn start:stagingyarn android:staging or yarn ios:staging
Productionyarn start:prodyarn android:prod or yarn ios:prod

Building blocks

PiecePlatformResponsibility
.env filesBothAPI URLs, keys, feature flags
react-native-configBothInjects env vars into native + JS at build time
Product flavorsAndroiddev, staging, prod build variants
Xcode schemesiOSApp-Dev, App-Staging, App-Prod
Yarn scriptsBothWire Metro + native build to the right env

Read values in code

Prefer typed helpers in src/config/env.ts:

TypeScript
import { getApiBaseUrl, getAppEnv } from '@/config/env';

const baseUrl = getApiBaseUrl();
const env = getAppEnv(); // 'development' | 'staging' | 'production'

Add new keys to src/types/react-native-config.d.ts for TypeScript support.

Android setup

Android uses product flavors in android/app/build.gradle.

Step 1 — Map variants to env files

Add before dotenv.gradle is applied:

GROOVY
project.ext.envConfigFiles = [
  devDebug:       ".env",
  devRelease:     ".env",
  stagingDebug:   ".env.staging",
  stagingRelease: ".env.staging",
  prodDebug:      ".env.production",
  prodRelease:    ".env.production",
]

apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"

Step 2 — Define flavors

GROOVY
flavorDimensions "env"

productFlavors {
  dev {
    dimension "env"
    versionNameSuffix "-dev"
    resValue "string", "app_name", "MyApp Dev"
  }
  staging {
    dimension "env"
    versionNameSuffix "-staging"
    resValue "string", "app_name", "MyApp Staging"
  }
  prod {
    dimension "env"
    resValue "string", "app_name", "MyApp"
  }
}

app_name is referenced by AndroidManifest.xml via android:label="@string/app_name".

Step 3 — Enable Metro for debug flavors

Inside the react { } block:

GROOVY
debuggableVariants = ["devDebug", "stagingDebug", "prodDebug"]

Skip this and Metro won't attach to flavored debug builds.

Step 4 — Add run scripts

JSON
"android:dev":     "ENVFILE=.env react-native run-android --mode=devDebug",
"android:staging": "ENVFILE=.env.staging react-native run-android --mode=stagingDebug",
"android:prod":    "ENVFILE=.env.production react-native run-android --mode=prodDebug"

Variant names are camelCase: devDebug, not dev-debug.

Android build variants

A variant = flavor + build type:

Code
flavor   +  build type  =  variant
dev      +  debug        =  devDebug
staging  +  debug        =  stagingDebug
prod     +  release      =  prodRelease   ← Play Store

Store releases

Terminal
yarn android:prod:release   # APK  → android/app/build/outputs/apk/prod/release/
yarn android:prod:aab       # AAB  → android/app/build/outputs/bundle/prodRelease/

iOS setup

iOS uses Xcode schemes. Each scheme runs a script before build to pick the right .env file.

Step 1 — xcconfig files

Create ios/Config/Debug.xcconfig and Release.xcconfig:

Code
#include "../Pods/Target Support Files/Pods-<YourApp>/Pods-<YourApp>.debug.xcconfig"
#include? "../tmp.xcconfig"

tmp.xcconfig is generated at build time — do not commit it. Point your app target's Debug / Release configurations to these files in Xcode.

Step 2 — Environment script

ios/scripts/set-env-file.sh runs before each build. It:

  1. Tells react-native-config which .env file to use
  2. Generates ios/tmp.xcconfig with env variables
  3. Sets the app display name on the home screen

Step 3 — Create one scheme per environment

Xcode → Product → Scheme → Edit Scheme → Build → Pre-actions → +

SchemePre-action command
App-Dev"${SRCROOT}/scripts/set-env-file.sh" ".env"
App-Staging"${SRCROOT}/scripts/set-env-file.sh" ".env.staging"
App-Prod"${SRCROOT}/scripts/set-env-file.sh" ".env.production"

Check "Provide build settings from" → select your app target. Save schemes under xcshareddata/xcschemes/ so they are committed to git.

Step 4 — Add run scripts

JSON
"ios:dev":     "ENVFILE=.env react-native run-ios --scheme App-Dev",
"ios:staging": "ENVFILE=.env.staging react-native run-ios --scheme App-Staging",
"ios:prod":    "ENVFILE=.env.production react-native run-ios --scheme App-Prod --mode Release"

App Store / TestFlight

  1. Open ios/<YourApp>.xcworkspace in Xcode
  2. Select the production scheme
  3. Product → Archive

Complete script reference

ScriptLoadsAndroid variantiOS scheme
yarn start:dev.env
yarn android:dev.envdevDebug
yarn ios:dev.envApp-Dev
yarn start:staging.env.staging
yarn android:staging.env.stagingstagingDebug
yarn ios:staging.env.stagingApp-Staging
yarn start:prod.env.production
yarn android:prod.env.productionprodDebug
yarn ios:prod.env.productionApp-Prod
yarn android:prod:release.env.productionprodRelease (APK)
yarn android:prod:aab.env.productionprodRelease (AAB)

android:prod and ios:prod are for testing production config locally — not for store submission.

Adding a new environment (e.g. QA)

StepAndroidiOS
1Create .env.qaSame file
2Add qaDebug / qaRelease to envConfigFilesDuplicate a scheme → App-QA
3Add qa flavor in productFlavorsPre-action: set-env-file.sh ".env.qa"
4Add qaDebug to debuggableVariantsAdd display name in set-env-file.sh
5Add start:qa, android:qa, ios:qa scripts

Install multiple environments side by side

By default, all flavors share one bundle ID — only one can be installed at a time.

To install dev + staging together:

  • Android: add applicationIdSuffix ".dev" (etc.) per flavor
  • iOS: use different PRODUCT_BUNDLE_IDENTIFIER per scheme

Troubleshooting

ProblemFix
Wrong API URL at runtimeMetro and native build use different envs. Restart Metro with matching yarn start:*.
Env change not appliedRebuild the native app after editing .env.
Android: variant not foundUse camelCase: --mode=stagingDebug, not staging-debug.
Android: Metro won't connectAdd your debug variant to debuggableVariants in build.gradle.
iOS: wrong app nameClean build (⇧⌘K). Check scheme pre-action points to correct .env.
iOS: scheme missing in CIScheme must be in xcshareddata/xcschemes/ and marked Shared.
Android: empty Config valuesenvConfigFiles must be declared before dotenv.gradle.

Project file map

FilePurpose
.env, .env.staging, .env.productionSecrets & config (gitignored)
package.jsonstart:*, android:*, ios:* scripts
android/app/build.gradleFlavors, env mapping, debuggable variants
android/app/src/main/AndroidManifest.xmlandroid:label="@string/app_name"
ios/Config/Debug.xcconfigDebug base config
ios/Config/Release.xcconfigRelease base config
ios/scripts/set-env-file.shPicks env file per scheme
ios/<App>.xcodeproj/xcshareddata/xcschemes/Shared schemes (in git)
src/config/env.tsTyped JS access to env vars
src/types/react-native-config.d.tsTypeScript types for env keys

Closing thoughts

Multi-environment setups pay off the first time QA needs staging while you keep developing locally. The upfront Gradle and Xcode configuration is tedious, but the daily workflow becomes as simple as picking the right yarn script pair.

If you're shipping React Native to production, this pattern scales cleanly — from two developers to CI pipelines that archive the correct scheme per branch.

Published Jun 30, 2026 · 8 min read