react-native-config

Loads .env variables into React Native apps for iOS, Android, macOS, and Windows, keeping secrets and per-environment config out of source.

Library
npm
v1.7.2
4,956stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
83/100Excellent
Development Activity88
Maintenance72
Community72
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
66/100Good
Architecture78
Code Quality74
Innovation55
Learning Curve55

react-native-config exposes environment variables defined in a .env file to your React Native JavaScript code, and to the native layers underneath it: Android reads them off the generated BuildConfig (and Gradle configs can read them too), iOS/macOS can expose them to Info.plist and Xcode Build Settings via a generated xcconfig, and Windows apps can read them from generated C++ constants. This lets teams keep API URLs, feature flags, and other per-build settings out of hardcoded source, and switch between environments (.env.staging, .env.production) with a single ENVFILE variable at build time.

The library ships as an autolinked native module with Codegen-generated TurboModule bindings, so React Native 0.60+ apps need no manual linking step, and it targets the New Architecture by default. On Android it resolves the app’s BuildConfig package automatically by checking the declaring Application class’s namespace before falling back to the applicationId, so common Gradle product-flavor and applicationIdSuffix setups work without extra configuration.

What You Get

  • A single .env file whose keys are exposed as a typed Config object importable from JavaScript/TypeScript
  • Native bridging to Android BuildConfig and Gradle, iOS/macOS Info.plist and Build Settings via a generated xcconfig, and Windows C++ constants
  • Autolinking with Codegen-generated TurboModule bindings, so no manual native linking is required on React Native 0.60+
  • Per-environment builds via the ENVFILE variable (e.g. .env.staging, .env.production) without changing app code
  • Automatic Android BuildConfig package resolution that accounts for applicationIdSuffix and per-flavor applicationId overrides

Common Use Cases

  • Swapping API base URLs and feature flags between local, staging, and production mobile builds
  • Injecting build-time constants (API keys for non-sensitive third-party SDKs, app version flags) into native Android/iOS code without editing native files
  • Configuring per-environment values referenced from AndroidManifest.xml or Info.plist, such as a Google Maps API key
  • Keeping environment-specific config out of committed source while still making it available consistently across JS, Android, iOS, and Windows

Under The Hood

Architecture The JS entry point (index.js) calls a single TurboModule spec, codegen/NativeConfigModule, generated from a Codegen config in package.json (RNCConfigSpec), and reads its getConfig().config map once at import time. Each native platform independently generates that map at build time from the same .env file rather than parsing it at runtime: on Android, RNCConfigModuleImpl reflects over the app’s generated BuildConfig class (resolved via a namespace-then-applicationId fallback chain) and converts the fields with MapConverter; on iOS/macOS, Ruby build-phase scripts (ReadDotEnv.rb, BuildDotenvConfig.rb) parse .env and emit a generated Objective-C file (GeneratedDotEnv.m) consumed by RNCConfigModule/RNCConfig; on Windows, an analogous codegen path emits C++ constants under the RNCConfigCodegen namespace. Because each platform’s generation pipeline is independent (Gradle plugin, Ruby/Xcode build phase, C++ codegen), the platforms share only a convention, not code — a change to the core .env-to-native-constant contract has to be made in three places.

Tech Stack Public API surface is TypeScript-typed JavaScript (index.js/index.d.ts); native bridging code is Java/Kotlin-adjacent Java (Android), Objective-C/Objective-C++ (iOS/macOS), and C++ (Windows), all wired together via React Native’s Codegen/TurboModule system declared in package.json. Build-time .env parsing is implemented in Ruby (BuildDotenvConfig.rb, ReadDotEnv.rb, BuildXCConfig.rb) invoked from Xcode Run Script build phases, and via a Gradle script (dotenv.gradle) applied from android/app/build.gradle. Peer dependencies are react, react-native, and an optional react-native-windows; CI runs three separate workflows (Example app JS/Android build, iOS Codegen tests, Windows CI).

Code Quality The Ruby .env-parsing and build-config-generation logic has a dedicated Minitest suite (read_dot_env_test.rb, build_dotenv_config_test.rb, rnc_config_diagnostic_test.rb) run via test/run.rb, which explicitly guards against a stray /tmp/envfile left by a real Xcode build corrupting test results. The native Android/iOS/Windows bridge modules themselves have no dedicated unit tests in this repo — correctness there is exercised indirectly through the Example app’s CI build jobs rather than direct assertions. Error handling favors loud, diagnostic-first failures over silent ones: the JS entry point throws a message enumerating the specific likely causes when the native module resolves to null, and the Android implementation logs every BuildConfig package candidate it tried rather than returning an empty config silently. The public API is TypeScript-typed and extensible via a user-declared NativeConfig interface.

What Makes It Unique The library’s value is less a novel technique than a comprehensive, build-time solution to a genuinely awkward cross-platform problem: getting one .env file’s values into four different native representations (JS object, Android BuildConfig, iOS Info.plist/Build Settings, Windows C++ constants) without shipping secrets in the JS bundle or requiring a runtime file read. The most distinctive piece of engineering is the Android BuildConfig-package resolution fallback chain — explicit override, then the package of the class declaring Application, then applicationId — which correctly handles applicationIdSuffix and per-flavor applicationId setups that a naive getPackageName() lookup would silently break on.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search