Configuration

Manage environment variables, Expo configuration, and secrets with type safety.

Overview

This starter template uses a robust configuration system that combines:

  • Environment Files: Manage variables for different environments (development, staging, production).
  • Zod Validation: Ensure all required variables are present and correctly formatted at runtime and build time.
  • Dynamic Expo Config: Automatically switch bundle IDs, app names, and other settings based on the environment.
  • Type-Safe Access: Access environment variables in your code with full TypeScript support.

Environment Files

The project supports multiple environments. By default, the following files are recognized:

  • .env.development: Used when APP_ENV=development (default).
  • .env.staging: Used when APP_ENV=staging.
  • .env.production: Used when APP_ENV=production.

If the specific environment file is missing, it falls back to .env.

.env.development
1APP_ENV=development
2EXPO_PUBLIC_API_URL=https://dev.api.example.com
3APP_NAME="My App (Dev)"

Type Validation (root-env.js)

All environment variables are validated using Zod in root-env.js. This prevents the app from starting if required variables are missing or invalid.

Variables are split into two categories:

  • Client: Variables available to the React Native app at runtime (e.g., API URLs).
  • Build Time: Secrets used only during the build process (e.g., API keys for upload, passwords). These are not included in the client bundle.
root-env.js
1const client = z.object({
2 APP_ENV: z.enum(["development", "staging", "production"]),
3 NAME: z.string().min(1),
4 EXPO_PUBLIC_API_URL: z.url(),
5});
6
7const buildTime = z.object({
8 SECRET_KEY: z.string().min(1).optional(),
9});

Expo Configuration (app.config.ts)

The app.config.ts file is the heart of your Expo project's configuration. Unlike a static app.json, this TypeScript file allows for dynamic configuration based on environment variables, enabling different setups for development, staging, and production.

Dynamic Config Function

The file exports a function that receives the default config context. We spread ...config to preserve any base settings and then override specific properties with our environment-aware values.

app.config.ts
1export default ({ config }: ConfigContext): ExpoConfig => ({
2 ...config,
3 name: Env.NAME, // "My App (Dev)" vs "My App"
4 slug: Env.SLUG, // "my-app"
5 version: Env.VERSION,// "1.0.0"
6 scheme: Env.SCHEME, // "myapp"
7 // ...
8});

Platform Specifics

iOS Configuration

Configures the bundle identifier (which changes per environment) and enables tablet support.

app.config.ts
1ios: {
2 supportsTablet: true,
3 bundleIdentifier: Env.BUNDLE_ID, // com.myapp.dev vs com.myapp
4},

Android Configuration

Sets up the adaptive icon with separate layers for a polished look on modern Android devices, enables edge-to-edge display, and defines the package name.

app.config.ts
1android: {
2 adaptiveIcon: {
3 backgroundColor: "#E6F4FE",
4 foregroundImage: "./assets/images/android-icon-foreground.png",
5 backgroundImage: "./assets/images/android-icon-background.png",
6 monochromeImage: "./assets/images/android-icon-monochrome.png",
7 },
8 edgeToEdgeEnabled: true, // Transparent status/nav bars
9 package: Env.PACKAGE, // com.myapp.dev vs com.myapp
10},

Web Configuration

Configures the web build to use Metro bundler and static output for better performance and SEO.

app.config.ts
1web: {
2 output: "static",
3 favicon: "./assets/images/favicon.png",
4 bundler: "metro",
5},

Plugins

Config-plugins extend the native capabilities of your app. This template comes pre-configured with:

  • expo-router: File-based routing.
  • expo-splash-screen: Controls the launch screen appearance and behavior.
app.config.ts
1plugins: [
2 "expo-router",
3 [
4 "expo-splash-screen",
5 {
6 image: "./assets/images/splash-icon.png",
7 imageWidth: 200,
8 resizeMode: "contain",
9 backgroundColor: "#ffffff",
10 dark: {
11 backgroundColor: "#000000",
12 },
13 },
14 ],
15],

Runtime Configuration (Extra)

The extra object is crucial. It passes the validated ClientEnv variables from your build environment into the React Native runtime. This is how you access API URLs and other constants in your JavaScript code.

app.config.ts
1extra: {
2 ...ClientEnv,
3 ...(Env.EAS_PROJECT_ID ? { eas: { projectId: Env.EAS_PROJECT_ID } } : {}),
4},

Experiments

Enables cutting-edge features for better performance and developer experience.

  • typedRoutes: Generates TypeScript types for your routes, ensuring type safety when navigating.
  • reactCompiler: Enables the experimental React Compiler (React Forget) for automatic memoization.
app.config.ts
1experiments: {
2 typedRoutes: true,
3 reactCompiler: true,
4},

Client-Side Usage

To use environment variables in your React Native code, import Env from @/lib/env. Do not use process.env directly in your components.

src/app/index.tsx
1import { Env } from "@/lib/env";
2
3console.log(Env.EXPO_PUBLIC_API_URL); // Typed and safe

Note: Env is frozen and read-only.

Adding a New Variable

  1. Add to .env files

    Add your variable to all relevant .env files.

    .env.development
    1MY_NEW_VAR=some_value
  2. Update Validation

    Add the variable to the client or buildTime schema in root-env.js.

    root-env.js
    1const client = z.object({
    2 // ...
    3 MY_NEW_VAR: z.string(),
    4});
    5
    6const _clientEnv = {
    7 // ...
    8 MY_NEW_VAR: process.env.MY_NEW_VAR,
    9};
  3. Use it

    Access it via Env.MY_NEW_VAR in your code.

Last updated on 2/10/2026

Edit this page on GitHub