Use the BABEL_ENV environment variable to switch between different .env files in react-native-dotenv. Configure your Babel plugin with a path template that includes ${BABEL_ENV}, then set this variable in your npm scripts. This approach allows you to maintain separate configurations for development, testing, and production without modifying your source code.
Understanding react-native-dotenv Configuration
Thereact-native-dotenv package is a Babel plugin that allows you to import environment variables directly from your .env files into your JavaScript modules. Unlike standard Node.js environments where process.env is available globally, React Native requires this transformation step because it does not automatically load environment files. The core benefit of this library is cleaner imports, such as import { API_URL } from '@env', rather than scattering require('react-native-config') or global lookups throughout your application.
In version 3.x and later, the configuration options have shifted slightly to align better with modern Babel standards. A common mistake developers make is trying to use complex path resolution logic within the Babel config that is ignored by the loader. Instead, the plugin relies on the presence of specific environment variables at build time to determine which file to parse. By leveraging the built-in BABEL_ENV variable, you can dynamically point to different files like .env.development or .env.production based on how you invoke your build command.
For more details on managing environment-specific builds, you might find Hitting breakpoints with Vite Preview useful, as the concept of distinct environments applies similarly across bundlers.
Step-by-Step Setup Guide
To implement this strategy, follow these steps to configure your project correctly.- Create Environment Files: Create separate files for each environment in your project root. For example:
.env.developmentcontainingAPI_URL=http://localhost:3000.env.productioncontainingAPI_URL=https://api.example.com
- Install Dependencies: Ensure you have the latest version installed. Run
npm install react-native-dotenv --save-dev. - Configure babel.config.js: Open your Babel configuration file. You need to add the
react-native-dotenvplugin to the plugins array. The critical part is setting thepathoption to include theBABEL_ENVplaceholder.
Note thatmodule.exports = function(api) { api.cache(true); return { presets: ['babel-preset-expo'], plugins: [ [ 'react-native-dotenv', { moduleName: '@env', path: '.env.${BABEL_ENV}', safe: false, allowUndefined: true, }, ], ], }; };safeshould be set tofalseif you are using dedicated environment files for each stage, as.env.localpatterns often conflict with strict safety checks. - Update Package Scripts: Modify your
package.jsonscripts to set theBABEL_ENVvariable before running the build commands.
When you run"scripts": { "start": "BABEL_ENV=development expo start", "android": "BABEL_ENV=development expo run:android", "ios": "BABEL_ENV=development expo run:ios", "build:prod": "BABEL_ENV=production expo export --platform android" }npm run android, the script setsBABEL_ENVtodevelopment. The Babel plugin then reads.env.development. When you runnpm run build:prod, it reads.env.production.
Common Pitfalls and Debugging
Even with correct configuration, issues can arise during the build process. One frequent error involves caching. Metro Bundler, used by Expo and React Native CLI, aggressively caches module resolutions. If you change your.env file but do not see the changes, clear the cache by deleting the node_modules/.cache directory or running npx expo start -c.
Another issue is related to TypeScript definitions. If you are using TypeScript, you may need to declare the @env module to avoid type errors. Create a file named env.d.ts in your src folder:
declare module '@env' {
export const API_URL: string;
export const APP_NAME: string;
}
If you encounter errors regarding missing exports, ensure that the variables defined in your .env file match exactly with those declared in your TypeScript definition file. Mismatches here will cause compilation failures.
For troubleshooting other module resolution issues in React Native, see How to Resolve: export 'default' (reexported as 'SyntheticPlatformEmitter') was not found in ""?.
Security Considerations
Never commit your.env files to version control, especially if they contain secrets. Add .env* to your .gitignore file. While react-native-dotenv works well for non-sensitive data like API endpoints, sensitive keys should ideally be handled via secure backend services or platform-specific key stores for maximum security. However, for most frontend-only applications, using separate env files with proper git ignore rules is sufficient.
Frequently Asked Questions
Can I use .env.local with react-native-dotenv?
Yes, but you must explicitly configure the path in your Babel plugin settings. Set the path option to .env.local or use a conditional logic if you want it to override other files. Note that .env.local is typically ignored by Git, making it ideal for local machine overrides.
Why are my environment variables undefined in production?
This usually happens because the production build script did not set the BABEL_ENV variable correctly, causing the plugin to look for a non-existent file. Check your package.json scripts to ensure BABEL_ENV=production is passed before the build command.
Does this work with Expo Go?
No, Expo Go does not support custom Babel plugins or environment variable injection at runtime. You must build a development build using EAS Build or locally generate an APK/IPA to test environment variable switching.
How do I handle missing variables gracefully?
Set allowUndefined: true in your Babel plugin configuration. This prevents the build from failing if a variable is referenced in code but missing from the selected .env file. You can then provide fallback values in your JavaScript code.
Is there a performance impact compared to process.env?
The performance difference is negligible. react-native-dotenv transforms imports at compile time, so the resulting bundle behaves similarly to hardcoded strings or direct object accesses. The main overhead occurs during the build phase, not at runtime.