@@ -25,8 +25,14 @@ To create a new project using this boilerplate template:
2525 git commit -m " Initial commit"
2626 ```
2727
28- 2 . ** Update project details** : Modify the ` app.json ` file to reflect your project's name, slug, and other configuration
29- details.
28+ 2 . ** Update project details** :
29+ - Open ` env.js ` and update the template values:
30+ - ` BUNDLE_ID ` : Your iOS Bundle ID / Android Package Name (e.g., ` com.yourcompany.yourapp ` )
31+ - ` NAME ` : Your app's display name (e.g., ` My Awesome App ` )
32+ - ` SLUG ` : Your Expo slug (e.g., ` my-awesome-app ` )
33+ - ` EXPO_ACCOUNT_OWNER ` : Your Expo account username
34+ - ` EAS_PROJECT_ID ` : Get by running ` npx eas init `
35+ - ` SCHEME ` : Your app's URL scheme for deep links (e.g., ` myapp ` )
3036
31373 . ** Install dependencies** :
3238
@@ -219,90 +225,148 @@ The logger automatically:
219225
220226## Environment Variables
221227
222- This project uses a centralized, type-safe environment variable system with Zod validation.
228+ This project uses a centralized, type-safe environment variable system with Zod validation, managed through ` env.js ` and
229+ ` app.config.ts ` .
230+
231+ ### Architecture
232+
233+ - ** ` env.js ` ** (root): Loads and validates environment variables, manages app configuration
234+ - ** ` app.config.ts ` ** : TypeScript config that uses validated variables from ` env.js `
235+ - ** ` src/env.js ` ** : Client-side access point - imports via ` @/env ` in your code
236+ - ** ` .env.{environment} ` ** : Environment-specific variables (development, staging, production)
223237
224238### Configuration
225239
226- 1 . ** Copy the environment template** :
240+ 1 . ** Update app configuration** in ` env.js ` :
241+
242+ Follow the template instructions to set your:
243+ - Bundle ID, app name, slug
244+ - Expo account owner and EAS project ID
245+ - URL scheme for deep links
246+
247+ 2 . ** Create environment file** :
227248
228249 ``` bash
229- cp .env.example .env.local
250+ cp .env.example .env.development
230251 ```
231252
232- 2 . ** Configure your variables** in ` .env.local ` :
253+ 3 . ** Configure your variables** in ` .env.development ` :
233254
234255 ``` bash
235256 # API Configuration
236257 EXPO_PUBLIC_API_URL=https://your-api.com
237258 EXPO_PUBLIC_DEFAULT_LOCALE=en
238259
239- # MMKV Encryption (32+ characters required )
260+ # MMKV Encryption (generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" )
240261 EXPO_PUBLIC_AUTH_STORAGE_ENCRYPTION_KEY=your-secure-random-64-character-hex-key
241262
242263 # Sentry (optional)
264+ EXPO_PUBLIC_SENTRY_DSN=https://your-sentry-dsn@sentry.io/project-id
243265 SENTRY_ORG=your-sentry-org
244266 SENTRY_PROJECT=your-project
245267 SENTRY_AUTH_TOKEN=your-token
246268 ```
247269
248- 3 . ** Start the app** - environment variables are automatically validated on startup
270+ 4 . ** Start the app** - environment variables are automatically validated on startup
271+
272+ ### Multiple Environments
273+
274+ The project supports multiple app variants on the same device:
275+
276+ - ** Development** : ` .env.development ` → ` yourapp-dev:// ` → "YourApp (Dev)"
277+ - ** Staging** : ` .env.staging ` → ` yourapp-staging:// ` → "YourApp (Staging)"
278+ - ** Production** : ` .env.production ` → ` yourapp:// ` → "YourApp"
279+
280+ Switch environments:
281+
282+ ``` bash
283+ APP_ENV=staging npm start
284+ APP_ENV=production npm start
285+ ```
249286
250287### Usage in Code
251288
252- ** Always import from ` @/env ` instead of using ` process.env ` directly :**
289+ ** Always import from ` @/env ` - ESLint enforces this :**
253290
254291``` typescript
255- // ✅ Correct
292+ // ✅ Correct - Import from @/env
256293import { env } from ' @/env' ;
257294
258295const apiUrl = env .EXPO_PUBLIC_API_URL ;
259296const locale = env .EXPO_PUBLIC_DEFAULT_LOCALE ;
297+ const appName = env .NAME ; // Dynamically generated based on APP_ENV
298+ const scheme = env .SCHEME ; // Environment-specific URL scheme
260299
261- // ❌ Wrong - ESLint will prevent this
300+ // ❌ Wrong - Direct process.env access (ESLint error)
262301const apiUrl = process .env .EXPO_PUBLIC_API_URL ;
302+
303+ // ❌ Wrong - Importing root env.js (ESLint error)
304+ import { ClientEnv } from ' ../../env' ;
305+
306+ // ✅ Platform detection - Use Platform.OS instead
307+ import { Platform } from ' react-native' ;
308+ if (Platform .OS === ' ios' ) {
309+ /* ... */
310+ }
263311```
264312
265313### Adding New Variables
266314
267315** For client-side variables** (accessible in the app):
268316
269- 1 . Add the variable to ` .env.local ` with ` EXPO_PUBLIC_ ` prefix
270- 2 . Update the schema in ` src/env.ts ` :
271- ``` typescript
272- const clientEnvSchema = z .object ({
317+ 1 . Add to ` .env.development ` with ` EXPO_PUBLIC_ ` prefix:
318+
319+ ``` bash
320+ EXPO_PUBLIC_YOUR_NEW_VAR=your-value
321+ ```
322+
323+ 2 . Update the schema in ` env.js ` :
324+
325+ ``` javascript
326+ const client = z .object ({
273327 // ... existing vars
274- EXPO_PUBLIC_YOUR_NEW_VAR: z .string ().min ( 1 , ' EXPO_PUBLIC_YOUR_NEW_VAR is required ' ),
328+ EXPO_PUBLIC_YOUR_NEW_VAR : z .string ().optional (), // or .min(1) for required
275329 });
276330 ```
277- 3 . Add explicit reference in ` validateClientEnv() ` :
278- ``` typescript
279- const envVars = {
331+
332+ 3 . Add to ` _clientEnv ` object:
333+
334+ ``` javascript
335+ const _clientEnv = {
280336 // ... existing vars
281337 EXPO_PUBLIC_YOUR_NEW_VAR : process .env .EXPO_PUBLIC_YOUR_NEW_VAR ,
282338 };
283339 ```
284- 4 . Restart the app - validation will run automatically
285340
286- ** For server-side variables** (build-time/CI only):
341+ 4 . Restart with clear cache:
342+ ``` bash
343+ npm start -- --clear
344+ ```
345+
346+ ** For build-time variables** (app.config.ts only):
287347
288- 1 . Add the variable to ` .env.local ` (no ` EXPO_PUBLIC_ ` prefix)
289- 2 . Access directly via ` process.env.YOUR_VAR ` in scripts or config files
290- 3 . Not validated or accessible in client code
348+ 1 . Add to ` .env.development ` (no ` EXPO_PUBLIC_ ` prefix)
349+ 2 . Update ` buildTime ` schema in ` env.js `
350+ 3 . Add to ` _buildTimeEnv ` object
351+ 4 . Access via ` Env ` in ` app.config.ts ` only
291352
292353### Security Notes
293354
294- - ` .env.local ` is git-ignored and never committed
295- - ** Client-side variables** (prefixed with ` EXPO_PUBLIC_ ` ):
296- - Validated on app startup via ` src/env.ts `
297- - Embedded in the app bundle and accessible at runtime
298- - Used for: API URLs, default locale, MMKV encryption key, Sentry DSN
299- - ** Must have ` EXPO_PUBLIC_ ` prefix** to be inlined by Metro bundler
300- - ** Server-side variables** (no prefix):
301- - NOT validated in ` src/env.ts ` to avoid build failures
302- - Only available during build-time, CI, and in Node.js scripts
303- - Used for: Sentry build tokens, build configuration
304- - NOT accessible in client code
305- - ESLint enforces centralized env usage (no direct ` process.env ` access in src/)
355+ - ` .env.* ` files are git-ignored and never committed (only ` .env.example ` is tracked)
356+ - ** Client-side variables** (` EXPO_PUBLIC_ ` prefix):
357+ - Validated via Zod schema in ` env.js `
358+ - Embedded in app bundle - accessible at runtime
359+ - Passed to app via ` app.config.ts ` → ` extra ` field → ` @/env `
360+ - Examples: API URLs, locale, MMKV key, Sentry DSN
361+ - ** Build-time variables** (no prefix):
362+ - Only available in ` app.config.ts ` and build scripts
363+ - Not embedded in app bundle
364+ - Examples: Sentry auth token, EAS project ID
365+ - ** ESLint enforcement** :
366+ - ✅ Allows: ` import { env } from '@/env' `
367+ - ❌ Blocks: ` process.env.* ` in ` src/ `
368+ - ❌ Blocks: Relative imports to root ` env.js `
369+ - ✅ Exception: ` Platform.OS ` for platform detection
306370
307371## Learn more
308372
0 commit comments