RORK LABJP
MAX — Rork Max generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessageNATIVE — Rork Max unlocks native features: AR/LiDAR, Metal 3D, widgets, Dynamic Island, Live Activities, HealthKit, and Core MLSTACK — Rork builds native iOS and Android apps with React Native (Expo) from a plain-English descriptionGROWTH — Rork now attracts over 743,000 monthly visits, growing at an 85% ratePRICE — Rork is free to start, with paid plans from $25/monthTREND — Gartner projects 75% of new apps will be built with low-code/no-code by the end of 2026MAX — Rork Max generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessageNATIVE — Rork Max unlocks native features: AR/LiDAR, Metal 3D, widgets, Dynamic Island, Live Activities, HealthKit, and Core MLSTACK — Rork builds native iOS and Android apps with React Native (Expo) from a plain-English descriptionGROWTH — Rork now attracts over 743,000 monthly visits, growing at an 85% ratePRICE — Rork is free to start, with paid plans from $25/monthTREND — Gartner projects 75% of new apps will be built with low-code/no-code by the end of 2026
Articles/Dev Tools
Dev Tools/2026-04-10Intermediate

How to Fix Rork App Environment Variable Errors — Troubleshooting .env Files and EAS Secrets Configuration

Fix environment variables returning undefined in your Rork app. Troubleshoot EXPO_PUBLIC_ prefix issues, .env file errors, Metro cache problems, and EAS environment variables (formerly EAS Secrets) step by step.

troubleshooting66error7fix7environment-variables2expo10easconfiguration

When Environment Variables Return Undefined in Rork Apps

The first time I wired Supabase into a Rork-generated app for one of my own indie projects, the URL I had carefully placed in .env came back as undefined at runtime. The code was correct. The file existed. I spent the better part of an hour ruling things out before finding the culprit: Metro's bundler cache.

Expo and React Native handle environment variables in a way that surprises most developers — values are statically embedded at build time, not read at runtime. If you bring server-side or web-development instincts to this, you will trip somewhere. The good news is that once you internalize this one mechanism, every undefined error narrows down to a handful of causes.

Common Causes of Environment Variable Errors

There are five main reasons environment variables fail to load in Rork apps built on Expo and React Native.

1. Missing EXPO_PUBLIC_ Prefix

Since Expo SDK 49, any environment variable you want to access on the client side must start with EXPO_PUBLIC_. Variables without this prefix are intentionally excluded from the app bundle for security reasons. In my experience, this single issue accounts for more than half of all undefined errors.

2. Incorrect .env File Location

The .env file needs to be in the project root directory. Placing it in a subdirectory like src/ or config/ means it won't be picked up by the bundler.

3. Stale Metro Bundler Cache

Metro caches environment variable values at build time. Changing your .env file without clearing the cache means the app continues using the old values. This was my mistake in the story above — if a fix you know is correct refuses to take effect, suspect the cache first.

4. Confusing Build-time and Runtime Variables

EAS Build environment variables (set in eas.json or via EAS's environment variable management) and local .env files operate through different mechanisms. Understanding when each is used prevents a whole class of configuration bugs where the build succeeds but the app can't read its variables.

5. EAS Secrets Misconfiguration

Variables registered with EAS may not be injected into the build process if they're assigned to the wrong environment (development / preview / production) or named incorrectly.

Step-by-Step Fix

Step 1: Verify Your .env File

Start by confirming your .env file is in the right place with the correct format.

# Make sure you're in the project root
ls -la .env*
 
# Check the contents
cat .env

Here's what a properly formatted .env file looks like:

# ✅ Correct: client-side variables with EXPO_PUBLIC_ prefix
EXPO_PUBLIC_API_URL=https://api.example.com
EXPO_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
EXPO_PUBLIC_SUPABASE_ANON_KEY=your_anon_key_here
 
# ✅ Correct: server-side / build-time only variables
SENTRY_AUTH_TOKEN=your_sentry_token
EAS_PROJECT_ID=your_project_id

And here are the most common formatting mistakes:

# ❌ Wrong: missing the EXPO_PUBLIC_ prefix
API_URL=https://api.example.com
SUPABASE_URL=https://your-project.supabase.co
 
# ❌ Wrong: quoting the value
EXPO_PUBLIC_API_URL="https://api.example.com"
 
# ❌ Wrong: spaces around the equals sign
EXPO_PUBLIC_API_URL = https://api.example.com

Step 2: Check How You Reference Variables in Code

Expo exposes environment variables through process.env.

// ✅ Correct way to reference variables
const apiUrl = process.env.EXPO_PUBLIC_API_URL;
const supabaseUrl = process.env.EXPO_PUBLIC_SUPABASE_URL;
 
// Debugging: confirm the values are loaded
console.log('API URL:', process.env.EXPO_PUBLIC_API_URL);
console.log('Supabase URL:', process.env.EXPO_PUBLIC_SUPABASE_URL);

One subtle but important detail: dynamic key access like process.env[name] does not work. The build-time static analysis looks for the literal expression process.env.EXPO_PUBLIC_API_URL and replaces it with the value, so you must use direct dot notation.

You may also see older approaches using react-native-dotenv or expo-constants, but since Expo SDK 49 the process.env approach is the recommended one.

// ⚠️ Legacy approach (still works, not recommended)
import Constants from 'expo-constants';
const apiUrl = Constants.expoConfig?.extra?.apiUrl;
 
// ✅ Modern approach (recommended)
const apiUrl = process.env.EXPO_PUBLIC_API_URL;

Step 3: Clear the Metro Cache

After changing your .env file, always clear Metro's cache before restarting.

# Clear the Metro cache and restart
npx expo start --clear
 
# For a more thorough reset
rm -rf node_modules/.cache
npx expo start --clear

Running plain npx expo start after editing .env will keep serving the old values, because environment variables are baked in at build time. I rewrote my .env several times before learning this, convinced the file itself was wrong. Make it muscle memory: touch .env, run --clear.

Step 4: Configure Environment Variables for EAS Build

For cloud builds with EAS Build, environment variables must be declared explicitly — for example in eas.json.

{
  "build": {
    "development": {
      "env": {
        "EXPO_PUBLIC_API_URL": "https://dev-api.example.com",
        "EXPO_PUBLIC_SUPABASE_URL": "https://your-project.supabase.co"
      }
    },
    "preview": {
      "env": {
        "EXPO_PUBLIC_API_URL": "https://staging-api.example.com",
        "EXPO_PUBLIC_SUPABASE_URL": "https://your-project.supabase.co"
      }
    },
    "production": {
      "env": {
        "EXPO_PUBLIC_API_URL": "https://api.example.com",
        "EXPO_PUBLIC_SUPABASE_URL": "https://your-project.supabase.co"
      }
    }
  }
}

Separating development, staging, and production endpoints structurally prevents the classic accident of test data landing in your production database. As an indie developer shipping apps on my own, I know how tempting it is to skip environment separation, but once real user data starts flowing in after launch it's too late to untangle — set it up with your very first build.

Step 5: Managing Secrets — from eas secret to EAS Environment Variables

Sensitive values like API keys should never be written directly into eas.json. They belong in EAS's managed storage. Older guides use the eas secret:create command, but current versions of the EAS CLI have consolidated this into per-environment "EAS environment variables" (eas env:* commands), and the eas secret family is now considered deprecated.

# ✅ Current approach: EAS environment variables (registered per environment)
eas env:create --name EXPO_PUBLIC_SUPABASE_URL \
  --value "https://your-project.supabase.co" \
  --environment production --visibility plaintext
 
# Sensitive values should use secret visibility (unreadable after creation)
eas env:create --name SENTRY_AUTH_TOKEN \
  --value "your_sentry_token" \
  --environment production --visibility secret
 
# List registered variables
eas env:list --environment production
 
# Pull development variables into a local .env file (plaintext values only)
eas env:pull --environment development

The big improvement over the old Secrets system is eas env:pull, which lets you synchronize local and cloud values — making it far easier to catch the classic mismatch where the app works locally but the cloud build is missing a variable. If you're setting things up fresh, go straight to the eas env commands even if older documentation shows eas secret.

Verifying Your Configuration

Once you've applied the fixes, verify that the variables actually load.

// app/debug-env.tsx (temporary debugging file)
import { View, Text, ScrollView } from 'react-native';
 
export default function DebugEnvScreen() {
  const envVars = {
    API_URL: process.env.EXPO_PUBLIC_API_URL,
    SUPABASE_URL: process.env.EXPO_PUBLIC_SUPABASE_URL,
    SUPABASE_ANON_KEY: process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY
      ? '✅ Set (hidden)'
      : '❌ Not set',
  };
 
  return (
    <ScrollView style={{ padding: 20 }}>
      <Text style={{ fontSize: 20, fontWeight: 'bold', marginBottom: 16 }}>
        Environment Variables Debug
      </Text>
      {Object.entries(envVars).map(([key, value]) => (
        <View key={key} style={{ marginBottom: 12 }}>
          <Text style={{ fontWeight: 'bold' }}>{key}</Text>
          <Text style={{ color: value ? '#22c55e' : '#ef4444' }}>
            {value || 'undefined ❌'}
          </Text>
        </View>
      ))}
    </ScrollView>
  );
}

Delete this debug screen once you've confirmed everything works. Keeping it out of production builds is essential to avoid leaking configuration details.

Best Practices to Prevent Recurrence

Adopt these rules in your project so environment variable issues don't come back.

Never put real secrets behind EXPO_PUBLIC_

This is the most important rule, and the one most often missed. Variables with the EXPO_PUBLIC_ prefix are embedded in your JS bundle in plain text. Anyone who downloads your app from the store can extract them, so only values that are safe to publish belong there. A Supabase anon key is fine — it's designed to be public, with Row Level Security as the real gatekeeper. But the moment you put a service_role key or a payment provider's secret key behind EXPO_PUBLIC_, you have effectively shipped it to every user. Keep those values server-side only, for example in Supabase Edge Functions.

Include a .env.example in your repository

For your team — and your future self — keep a template of required variables in the repo.

# .env.example (values empty or dummy)
EXPO_PUBLIC_API_URL=
EXPO_PUBLIC_SUPABASE_URL=
EXPO_PUBLIC_SUPABASE_ANON_KEY=

Add .env to .gitignore

Never commit actual .env files with real credentials:

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

Create a Type-safe Environment Helper

In TypeScript projects, wrap your environment access in a utility that warns you about missing values early:

// utils/env.ts
function getEnvVar(key: string): string {
  const value = process.env[key];
  if (!value) {
    console.warn(`⚠️ Environment variable ${key} is not set`);
  }
  return value ?? '';
}
 
export const ENV = {
  API_URL: getEnvVar('EXPO_PUBLIC_API_URL'),
  SUPABASE_URL: getEnvVar('EXPO_PUBLIC_SUPABASE_URL'),
  SUPABASE_ANON_KEY: getEnvVar('EXPO_PUBLIC_SUPABASE_ANON_KEY'),
} as const;

Note that the dynamic lookup inside getEnvVar works here only because the static references inside the ENV object are what cause the values to be embedded at build time, as covered in Step 2. Using this ENV object throughout your app prevents typos in variable names and surfaces missing configuration as a warning right at startup.

Looking Back

When an environment variable comes back undefined, the checking order is always the same: ① the EXPO_PUBLIC_ prefix → ② the .env file's location and format → ③ npx expo start --clear → ④ the EAS-side environment configuration. In my experience, the vast majority of cases resolve at ① or ③.

As a next step, add a .env.example and the type-safe ENV helper to your project today. Troubleshooting isn't finished until you've built the guardrails that keep the same error from costing you an hour twice.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-04-08
Rork In-App Purchase Setup Errors: How to Fix IAP Not Working (StoreKit & RevenueCat)
Troubleshoot Rork app In-App Purchase errors: products not loading, sandbox test failures, RevenueCat API key issues, and StoreKit configuration problems. Step-by-step fixes for 12 common IAP errors.
Dev Tools2026-04-07
How to Fix Google Play Policy Violation Errors for Rork Apps
Is your Rork app getting rejected on Google Play due to policy violations? Learn the most common rejection causes and step-by-step solutions to get your app approved.
Dev Tools2026-05-23
Rork-Specific 'expo start --offline forbidden': Four Causes in Rork's Template Config
When expo start --offline returns 'forbidden' specifically on a Rork-generated project, the cause is usually Rork template config: tsconfigPaths, an un-generated expo-router cache, native prebuild, or a lockfile mismatch. Four Rork-specific fixes; the generic Expo proxy and dependency-validation guide is covered separately.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →