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 whenAPP_ENV=development(default)..env.staging: Used whenAPP_ENV=staging..env.production: Used whenAPP_ENV=production.
If the specific environment file is missing, it falls back to .env.
1APP_ENV=development2EXPO_PUBLIC_API_URL=https://dev.api.example.com3APP_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.
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});67const 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.
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.
1ios: {2 supportsTablet: true,3 bundleIdentifier: Env.BUNDLE_ID, // com.myapp.dev vs com.myapp4},
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.
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 bars9 package: Env.PACKAGE, // com.myapp.dev vs com.myapp10},
Web Configuration
Configures the web build to use Metro bundler and static output for better performance and SEO.
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.
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.
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.
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.
1import { Env } from "@/lib/env";23console.log(Env.EXPO_PUBLIC_API_URL); // Typed and safe
Note: Env is frozen and read-only.
Adding a New Variable
- Add to .env files
Add your variable to all relevant
.envfiles..env.development1MY_NEW_VAR=some_value - Update Validation
Add the variable to the
clientorbuildTimeschema inroot-env.js.root-env.js1const client = z.object({2 // ...3 MY_NEW_VAR: z.string(),4});56const _clientEnv = {7 // ...8 MY_NEW_VAR: process.env.MY_NEW_VAR,9}; - Use it
Access it via
Env.MY_NEW_VARin your code.
Last updated on 2/10/2026
Edit this page on GitHub