Environment Configuration and Secrets
Environment values (API URL, feature flags, keys of public SDKs) are inlined
at build time: there is no process.env at runtime on the device. In Expo,
variables prefixed with EXPO_PUBLIC_ are inlined into the JS bundle; in
bare apps, react-native-config reads .env files and exposes the values
to JS and native code. Everything that ends up in the app is not secret: the
bundle and the binary can be unpacked. Real secrets stay on a backend, and the
app gets short-lived tokens from it.
Applies to: Expo SDK 58.
Details
Tools
| Tool | Where values are available | Notes |
|---|---|---|
EXPO_PUBLIC_* in .env | JS | Read as process.env.EXPO_PUBLIC_API_URL; replaced with the literal at build |
app.config.ts (Expo) | Native config: bundle ID, app name, plugins | Can read any env variable at build time; extra is readable via expo-constants |
| EAS environment variables | Builds and updates on EAS | Per environment (development, preview, production); "sensitive" and "secret" ones are hidden in logs, but still end up in the app if the code uses them |
react-native-config | JS, Android (BuildConfig, Gradle), iOS (Info.plist, Objective-C) | ENVFILE=.env.staging selects the file |
react-native-dotenv | JS only | A Babel plugin |
What Is a Secret
| Can be in the app | Must stay on the server |
|---|---|
| API base URL, feature flags | Payment provider secret keys |
| Public keys of SDKs: Maps, Sentry DSN, analytics | Database credentials, admin tokens |
| OAuth client ID | OAuth client secret, third-party API secrets |
Build-time secrets (keystore passwords, App Store Connect API key, EAS token) belong in CI secrets or EAS credentials, not in the repository.
One Environment per Build
Build a separate binary per environment (separate EAS build profiles, Gradle flavors or Xcode schemes) and load only its values. A production build that also contains staging URLs and keys leaks them.
Example
EXPO_PUBLIC_API_URL=https://api.example.com
// Expo: inlined at build time
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
react-native-config in JS and native code
import Config from 'react-native-config';
const apiUrl = Config.API_URL;
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"
android {
defaultConfig {
applicationId project.env.get("APP_ID")
}
}
// Android: values become BuildConfig fields
String apiUrl = BuildConfig.API_URL;
On iOS, use $(API_URL) in Info.plist or [RNCConfig envFor:@"API_URL"] in
Objective-C.
Follow-up Questions
- Where do you store a user's token on the device? In the Keychain / Keystore, not in AsyncStorage; see Storing Data.
- How do you change the API URL without a rebuild? Publish an OTA update
(
EXPO_PUBLIC_values are in the JS bundle) or fetch the configuration from the backend at runtime; values in native code need a new build.
Common Mistakes
- Putting an API secret in
.envand thinking it is hidden: it is inlined into the bundle as plain text. - Expecting
process.env.API_URLwithout theEXPO_PUBLIC_prefix to work in Expo app code: it isundefined. - Reading env variables dynamically (
process.env[name]): only static references are inlined.