How Environment Variables Work in Create React App: Build-Time Injection Explained

Create React App injects environment variables at build time by loading .env files, filtering for the REACT_APP_ prefix, and embedding the values into your JavaScript bundle using webpack's DefinePlugin, making them accessible via process.env in your React code.

Create React App (CRA) from the facebook/create-react-app repository provides a zero-configuration system for managing environment variables in Create React App through a secure, compile-time injection pipeline. Understanding how these variables flow from your .env files into the running application requires examining the build process orchestrated by the react-scripts package.

The Environment Variable Pipeline in react-scripts

The core logic resides in packages/react-scripts/config/env.js, which executes during the build to transform your environment files into injectable code. This module handles file discovery, variable filtering, and webpack configuration generation in a strict sequence.

Loading and Parsing .env* Files

CRA automatically discovers and loads environment files based on your current NODE_ENV. According to the source code in env.js (lines 25‑34), the framework looks for files in this specific order:

  • .env
  • .env.local
  • .env.development or .env.production (depending on the current environment)
  • .env.development.local or .env.production.local

Each existing file is parsed using dotenv and dotenv-expand (lines 36‑48) to resolve variable references and expand values before injection.

Normalizing NODE_PATH

Before processing variables, CRA resolves any relative paths defined in NODE_PATH to ensure consistent module resolution across monorepos (lines 51‑65). This normalization step guarantees that custom import paths work correctly regardless of where the build command is executed.

The REACT_APP_ Prefix Filter

Not all environment variables reach your React code. The env.js module enforces a strict naming convention using the RegExp /^REACT_APP_/i (line 69) to filter variables. Only names matching this pattern (case-insensitive) are retained for the client bundle.

NODE_ENV is automatically included in the final environment object regardless of prefix, providing access to 'development', 'production', or 'test' values within your application logic.

Build-Time Injection via getClientEnvironment

The getClientEnvironment(publicUrl) function (line 71) constructs the final environment object consumed by webpack. This function returns two critical structures:

  • raw (lines 72‑83): Contains the plain string values that will be accessible in your application code
  • stringified (lines 101‑107): Contains JSON-stringified versions of each value for safe injection into webpack's DefinePlugin

When webpack processes your code, the DefinePlugin replaces all occurrences of process.env.REACT_APP_VARIABLE_NAME with the actual string value at compile time. This means the final bundle contains hardcoded strings rather than runtime environment lookups.

Using Environment Variables in Your Application

Once configured in your .env files, variables become accessible throughout your React components and utility modules.

Accessing Variables in JavaScript

Define your variables in the project root:


# .env

REACT_APP_API_URL=https://api.example.com
REACT_APP_ENABLE_ANALYTICS=true

Access them using the global process.env object:

// src/config/api.js
const apiUrl = process.env.REACT_APP_API_URL;
const analyticsEnabled = process.env.REACT_APP_ENABLE_ANALYTICS === 'true';

export { apiUrl, analyticsEnabled };

Injecting Variables into HTML

Starting with [email protected], you can reference environment variables directly in public/index.html using the %VARIABLE_NAME% syntax:

<!-- public/index.html -->
<title>%REACT_APP_SITE_TITLE%</title>
<meta name="description" content="%REACT_APP_SITE_DESCRIPTION%">

During the build process, CRA replaces these placeholders with the corresponding environment values, allowing dynamic meta tags and titles without ejecting.

Environment-Specific Configuration

CRA supports cascading configuration files that allow different settings for development and production environments. When you run npm start, CRA loads .env.development; when you run npm run build, it loads .env.production.

Create environment-specific overrides:


# .env.production

REACT_APP_API_URL=https://api.prod.example.com
REACT_APP_DEBUG_MODE=false

Local overrides using the .local suffix take precedence and should be added to .gitignore to prevent committing sensitive machine-specific values:


# .env.local

REACT_APP_LOCAL_SECRET=development-key-only

Temporary Shell Variables

For one-off testing without modifying files, you can prefix the npm command on Unix systems:

REACT_APP_TEMP_TOKEN=abc123 npm start

Security and Performance Considerations

Build-time embedding means environment variables become literal strings in your static JavaScript files. This architecture provides two critical constraints:

  1. No runtime changes: You cannot modify environment variables after building the application without triggering a new build
  2. No secrets in client code: Because values are visible in the bundled JavaScript served to browsers, CRA documentation explicitly warns against storing API keys, database credentials, or other secrets in REACT_APP_ variables

The webpack DefinePlugin configuration generated by env.js looks conceptually like this:

// Simplified representation of the internal webpack configuration
new webpack.DefinePlugin({
  'process.env': {
    NODE_ENV: '"production"',
    PUBLIC_URL: '"/"',
    REACT_APP_API_URL: '"https://api.example.com"',
    REACT_APP_ENABLE_ANALYTICS: '"true"'
  }
});

Summary

  • Environment variables in Create React App are processed at build time by packages/react-scripts/config/env.js, not at runtime
  • Only variables prefixed with REACT_APP_ are exposed to the client bundle, alongside the automatic NODE_ENV and PUBLIC_URL
  • The system loads .env files in a specific precedence order, with .local variants overriding base files
  • Values are embedded via webpack's DefinePlugin, transforming process.env.VAR_NAME references into static strings in the final bundle
  • Variables can be injected into public/index.html using the %REACT_APP_VAR% placeholder syntax
  • Never store secrets in REACT_APP_ variables since they become visible in the compiled static assets

Frequently Asked Questions

Why do my environment variables need the REACT_APP_ prefix?

Create React App intentionally filters environment variables using the RegExp /^REACT_APP_/i (as implemented in env.js line 69) to prevent accidental exposure of system-level variables to the client-side bundle. This security measure ensures that only explicitly intended variables become part of your public JavaScript code, while server-side or shell variables remain isolated from the browser.

Can I change environment variables after building the app?

No. Because CRA embeds environment variables at build time using webpack's DefinePlugin, the values become hardcoded strings in your static JavaScript files. To change a variable's value, you must rebuild the application. This differs from server-side Node.js applications where process.env is evaluated at runtime.

How do I use environment variables in public/index.html?

Starting with [email protected], you can reference any REACT_APP_ variable in public/index.html using the percent-sign syntax: %REACT_APP_VARIABLE_NAME%. During the build process, CRA replaces these placeholders with the actual values from your environment files, enabling dynamic titles, meta tags, and CDN URLs without ejecting from the default configuration.

What is the order of precedence for .env files in Create React App?

CRA loads files in a specific sequence where later files override earlier ones: first .env, then .env.local, followed by .env.development or .env.production (depending on the current NODE_ENV), and finally .env.development.local or .env.production.local. This hierarchy allows you to define default values in .env while overriding them with environment-specific or local-only settings.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →