
OWASP's MASWE-0005 confirms the risk: API keys hardcoded in app packages or compiled binaries can be extracted through reverse engineering. That's not theoretical — it's a well-documented attack surface.
This guide covers everything you need to manage environment variables properly in React Native: installing and configuring react-native-config, setting up Android Flavors and iOS Schemes, accessing variables in native code, and securing secrets across CI/CD pipelines.
TL;DR
react-native-configreads.envfiles at build time, making variables available in JavaScript, native Android, and native iOS code- Multiple environments are supported through Android Product Flavors and iOS Schemes — not just JS-layer workarounds
- Unlike
react-native-dotenv, variables are accessible insideAndroidManifest.xml,Info.plist, Gradle, and native Swift/Kotlin files - Always add
.envfiles to.gitignoreand run a clean build after any variable changes
What Is react-native-config and Why Use It
react-native-config is an open-source library (currently at v1.6.1, with ~353,000 weekly npm downloads) that reads variables from .env files and makes them available across your entire React Native app — JavaScript, Android native code, iOS native code, and build configuration files.
The key distinction from runtime approaches: variables are resolved at build time, meaning they're baked into the binary during compilation, not fetched or injected at runtime.
Core Use Cases
- API base URL switching — point dev builds at
http://localhost:3000and production at your live API without touching source code - Third-party SDK keys — inject Google Maps API keys, Firebase config, and analytics SDK tokens into native layers
- Feature flags and logging levels — change app behaviour per environment without code changes or over-the-air updates
These use cases stay relevant as the React Native ecosystem evolves — including the shift to the New Architecture.
New Architecture Compatibility
As of v1.6.0 (released November 2025), react-native-config officially supports TurboModules and React Native's New Architecture on both Android and iOS. If your team is upgrading to React Native 0.73+, this library is forward-compatible. No need to swap it out later.
react-native-config vs react-native-dotenv: Key Differences
The fundamental split comes down to where variables are available. react-native-dotenv is a Babel plugin that inlines environment variables into the JavaScript bundle at Metro build time — that's its entire scope. react-native-config also exposes variables to native Android and iOS code — the only option when a native SDK needs a key at compile time.
| Feature | react-native-config | react-native-dotenv |
|---|---|---|
| JS/TS access | import Config from 'react-native-config' |
import { VAR } from '@env' |
| Native Android access | ✅ BuildConfig.API_URL in Java/Kotlin |
❌ Not supported |
| AndroidManifest.xml | ✅ Via @string/KEY |
❌ Not supported |
| iOS Info.plist | ✅ Via $(MY_VAR) placeholder |
❌ Not supported |
| Setup complexity | Medium (native config required) | Low (Babel plugin only) |
| TypeScript support | Via .d.ts declaration file |
Via module declaration |

When to Choose Which
- Use
react-native-dotenvif your app only reads env vars in JavaScript — a simplefetch()call with a base URL, for example. The setup is lighter and faster. - Use
react-native-configif any native SDK requires a key at compile time. Google Maps, Firebase, analytics SDKs — these all initialise at the native layer and need their keys before your JS bundle loads.
Step-by-Step Installation and Setup
Installation
# Yarn (recommended in official docs)
yarn add react-native-config
# npm
npm install react-native-config
# iOS — install pods
cd ios && pod install
React Native 0.60+ uses autolinking, so no manual react-native link step is needed.
Creating .env Files
Create these files at your project root:
.env # default fallback
.env.development
.env.staging
.env.production
Sample content:
API_URL=https://api.example.com
API_KEY=your_api_key_here
GOOGLE_MAP_API_KEY=your_maps_key
Variable names must be consistent across all env files. A missing key silently returns undefined, which is a common source of production bugs.
Android Native Setup
Make two additions to android/app/build.gradle:
1. Map flavors to env files:
project.ext.envConfigFiles = [
debug: ".env.development",
release: ".env.production",
staging: ".env.staging",
]
2. Apply the dotenv plugin (add near the top of the file):
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
If your applicationId differs from the package in AndroidManifest.xml, add this inside defaultConfig:
resValue "string", "build_config_package", "com.yourapp"
Android Product Flavors
Define flavors in build.gradle so staging and production builds can coexist on the same device:
flavorDimensions "version"
productFlavors {
staging {
dimension "version"
applicationIdSuffix ".staging"
resValue "string", "app_name", "MyApp Staging"
}
production {
dimension "version"
applicationId "com.yourapp"
resValue "string", "app_name", "MyApp"
}
}
iOS Xcode Scheme Setup
iOS handles environment switching through Xcode schemes rather than build flavors. For each environment, create a new scheme via Product > Scheme > New Scheme in Xcode, then add a pre-build action under Build > Pre-actions:
"${SRCROOT}/../node_modules/react-native-config/ios/ReactNativeConfig/BuildXCConfig.rb" \
"${SRCROOT}/.." "${SRCROOT}/tmp.xcconfig"
Set "Provide build settings from" to your main app target — without this, the script cannot resolve paths correctly.
iOS xcconfig File
Create ios/Config.xcconfig with:
#include? "tmp.xcconfig"
Then assign Config.xcconfig to both Debug and Release configurations at the Project level (not the Target level) in your Xcode project settings. Add ios/tmp.xcconfig to .gitignore.
Accessing Config Variables in JavaScript, Android, and iOS
Once your .env file is set up, accessing those variables differs slightly across JavaScript, Android, and iOS. Here's how each layer works.
JavaScript and TypeScript
import Config from 'react-native-config';
const apiUrl = Config.API_URL;
const mapsKey = Config.GOOGLE_MAP_API_KEY;
For TypeScript type safety, create react-native-config.d.ts in your project:
declare module 'react-native-config' {
export interface NativeConfig {
API_URL?: string;
API_KEY?: string;
GOOGLE_MAP_API_KEY?: string;
}
export const Config: NativeConfig;
export default Config;
}
This gives you autocomplete and catches typos before they hit production.
Android Native (Java/Kotlin)
In native Android code, variables are exposed via the generated BuildConfig class:
// Java
String apiUrl = BuildConfig.API_URL;
// Access in Gradle
def appId = project.env.get("APP_ID")
To inject a Google Maps API key into your manifest, reference the generated string resource directly:
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/GOOGLE_MAP_API_KEY" />
The react-native-config dotenv plugin generates these string resources automatically from your .env file during the build.
iOS Native (Objective-C)
#import "RNCConfig.h"
NSString *apiUrl = [RNCConfig envFor:@"API_URL"];
This is useful for SDK initialization in AppDelegate before the React Native bridge loads. It keeps native setup logic in sync with your environment without hardcoding values.
iOS Info.plist
Reference env vars directly in Info.plist using the $(VAR_NAME) syntax:
<key>GMSApiKey</key>
<string>$(GOOGLE_MAP_API_KEY)</string>
<key>CFBundleDisplayName</key>
<string>$(APP_NAME)</string>
This keeps app display names, bundle IDs, and third-party SDK keys in sync across environments at build time, with no source code changes required between builds.

Managing Multiple Environments
Build Scripts in package.json
"scripts": {
"android:dev": "ENVFILE=.env.development react-native run-android",
"android:staging": "ENVFILE=.env.staging react-native run-android --variant stagingDebug",
"android:prod": "ENVFILE=.env.production react-native run-android --variant productionRelease",
"ios:dev": "ENVFILE=.env.development react-native run-ios",
"ios:staging": "ENVFILE=.env.staging react-native run-ios --scheme StagingScheme",
"ios:prod": "ENVFILE=.env.production react-native run-ios --scheme ProductionScheme"
}
On Windows, use SET ENVFILE=.env.staging && (cmd) or $env:ENVFILE=".env.staging"; (PowerShell) instead of the Unix prefix.
Using with Expo
react-native-config works with Expo's bare workflow only:
npx expo run:android --variant stagingDebug
npx expo run:ios --scheme StagingScheme
It does not work with Expo Go or the managed workflow. Expo's own documentation confirms that libraries requiring custom native code need a development build — Expo Go only includes libraries already part of the Expo SDK.
CI/CD Secret Injection
Never commit .env files containing real keys. The standard approach for production mobile builds is:
- Store each variable as an encrypted secret in your CI provider (GitHub Actions, Bitrise, CircleCI)
- Add a pipeline step that writes the correct
.envfile before the build runs - The generated file exists only during that build and is never persisted
GitHub Actions supports this via repository/environment secrets and the GITHUB_ENV environment file. Bitrise Secrets are encrypted, not shown in bitrise.yml, and not exposed in build logs. Your repo stays clean, and each environment gets exactly the variables it needs — nothing more.

Best Practices, Security, and Common Pitfalls
Security Rules
The react-native-config README is explicit: "it is basically impossible to prevent users from reverse engineering mobile app secrets." Variables end up in your app binary.
Follow these rules:
- Never store passwords, private keys, payment credentials, or signing secrets in
.envfiles - Safe to store: API base URLs, semi-public SDK keys (Google Maps, analytics), feature flag values, logging levels
- Add all
.envfiles to.gitignore - If using Proguard, add
-keep class com.mypackage.BuildConfig { *; }to your config to prevent stripping
Fixing Stale Variables
Once your secrets are secured, the next hurdle is a subtler one. The most common react-native-config issue: you update a .env value and nothing changes. This happens because variables are baked in at build time.
Full fix sequence:
# Android
cd android && ./gradlew clean
# iOS
cd ios && xcodebuild clean
pod install
# Both — reset Metro cache
npx react-native start --reset-cache
Run a fresh build after cleaning.
Team Consistency
- Commit a
.env.examplewith all required keys and placeholder values — this is the only env file that belongs in version control - Use SCREAMING_SNAKE_CASE for all variable names, consistently across every env file
- Document which variables are required for each environment in your project README
Frequently Asked Questions
What is the difference between react-native-config and react-native-dotenv?
react-native-dotenv is a Babel plugin that inlines variables into your JavaScript bundle only. react-native-config bridges to native Android and iOS code, making it the required choice when any native SDK needs an environment variable at compile time.
Why are my environment variables not updating after I change the .env file?
Variables are resolved at build time, not runtime. After any .env change, run through these steps in order:
- Clean the Android build:
gradlew clean - Clean the iOS build:
xcodebuild clean - Re-run
pod installon iOS - Restart Metro with
--reset-cache
Can I use react-native-config with Expo?
Yes, but only with Expo's bare workflow (ejected projects). Use npx expo run:android --variant stagingDebug or npx expo run:ios --scheme StagingScheme. It does not work with Expo Go or the managed workflow.
How do I add TypeScript support for react-native-config?
Create a react-native-config.d.ts file that declares the react-native-config module and extends the NativeConfig interface with all your expected variable keys. This gives you autocomplete and catches missing or mistyped variable names at compile time.
Should I commit my .env files to version control?
Never commit .env files containing real keys. Commit a .env.example with placeholder values instead, and inject actual secrets via CI/CD environment variables or a secrets manager during automated builds.
How do I access react-native-config variables in native Android or iOS code?
Access methods differ by platform:
- Android: Use
BuildConfig.API_KEYin Java/Kotlin, or@string/API_KEYinAndroidManifest.xml - iOS: Use
[RNCConfig envFor:@"API_KEY"]in Objective-C, or$(API_KEY)directly inInfo.plist


