React Native Environment Variables

An app runs in several settings during its life. Developers test against a practice server, testers check a staging copy, and real users connect to the production server. Each setting needs different addresses, keys, and switches. Environment variables hold those differences outside the code, so one codebase serves every setting without edits.

The Idea in Pictures

Same app code
      │
 ┌────┼─────────────┐
 ▼    ▼             ▼
Development   Staging        Production
api.dev.com   api.test.com   api.example.com
test keys     test keys       real keys
debug on      debug on        debug off

Why Not Hard-Code Values?

  • Changing a server address would force edits in many files
  • A forgotten test address could ship to real users
  • Secret keys placed in code end up in version control history
  • Different team members need different local settings

Using .env Files in Expo

Expo reads variables from .env files. Names that begin with EXPO_PUBLIC_ become available inside the app code.

# .env
EXPO_PUBLIC_API_URL=https://api.dev.example.com
EXPO_PUBLIC_APP_NAME=Notes Dev
EXPO_PUBLIC_ENABLE_LOGS=true
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
const showLogs = process.env.EXPO_PUBLIC_ENABLE_LOGS === 'true';

console.log('Calling', apiUrl);

Variables arrive as text. Convert them yourself, so the string 'true' must be compared with === 'true'.

Several Files for Several Settings

.env                  Shared defaults
.env.development      Local development values
.env.production       Production values
.env.local            Personal overrides (never shared)

Expo loads .env first and layers the matching file on top, using the current build mode.

A Critical Safety Rule

Every value that ships inside the app bundle can be extracted by a determined person. The EXPO_PUBLIC_ prefix serves as a reminder. Use it only for values that are safe to expose, such as an API address or a public analytics key.

Safe in the app                   Never in the app
──────────────────────            ─────────────────────────────
API base URL                      Database admin password
Public map key (restricted)       Payment secret key
Feature flags                     Private signing keys
Analytics project ID              Server-to-server tokens

Keep real secrets on a server. The app calls the server, and the server uses the secret.

Keep .env Files Out of Version Control

Add private files to .gitignore.

# .gitignore
.env.local
.env*.local
.env.production

Commit a safe template named .env.example that lists variable names with empty values. New team members copy it and fill their own.

# .env.example
EXPO_PUBLIC_API_URL=
EXPO_PUBLIC_APP_NAME=

Central Config File

Read variables in one module. Screens import clean values and the project gains one place for defaults and checks.

// src/config/index.js
const required = (name, value) => {
  if (!value) {
    throw new Error(`Missing environment variable: ${name}`);
  }
  return value;
};

export const config = {
  apiUrl: required('EXPO_PUBLIC_API_URL', process.env.EXPO_PUBLIC_API_URL),
  appName: process.env.EXPO_PUBLIC_APP_NAME ?? 'Notes',
  enableLogs: process.env.EXPO_PUBLIC_ENABLE_LOGS === 'true',
};

The check stops the app early with a clear message instead of failing later with a confusing network error.

Per-Build Settings with app.config.js

Static app.json cannot read variables. A dynamic app.config.js file can, so each build gets its own name and identifier.

// app.config.js
const isProd = process.env.APP_ENV === 'production';

export default {
  expo: {
    name: isProd ? 'Notes' : 'Notes Dev',
    slug: 'notes',
    ios: { bundleIdentifier: isProd ? 'com.acme.notes' : 'com.acme.notes.dev' },
    android: { package: isProd ? 'com.acme.notes' : 'com.acme.notes.dev' },
  },
};

Different identifiers let testers install the development and production versions side by side.

Feature Flags

A flag switches a feature on or off per environment. A half-finished screen can ship hidden and appear only in staging.

{config.enableNewCheckout && <NewCheckoutButton />}

Build Service Variables

Cloud build services such as EAS store variables in project settings or in eas.json profiles, so secrets stay off developer laptops and out of the repository.

Common Mistakes

  • Editing .env without restarting the development server
  • Committing real keys to a public repository
  • Assuming a value is a boolean instead of text
  • Forgetting to set variables in the build service

Summary

Environment variables separate settings from code. Use EXPO_PUBLIC_ variables for values safe to expose, keep private files out of version control, centralize reading in a config module, and use app.config.js for per-build identity. Store real secrets on servers, never inside the app.