📅 Day 10
💡 tip

Environment Variables in React Native with Expo

Learn how to safely manage environment variables in Expo apps using .env files, EXPO_PUBLIC prefix, and EAS secrets

ExpoEnvironment VariablesSecurity

Welcome to Day 10 of the React Native Advent Calendar!

Today we’re tackling environment variables in Expo - a crucial topic that’s easy to get wrong. Learn how to use .env files properly and avoid the number one security mistake: exposing secrets in your frontend code!


Environment Variables Made Easy

Expo makes environment variables simple with automatic .env file support. Just create a .env file and prefix your variables with EXPO_PUBLIC_:

.env
# Your .env file
EXPO_PUBLIC_API_URL=https://api.example.com
EXPO_PUBLIC_APP_NAME=My Awesome App
EXPO_PUBLIC_FEATURE_FLAG=true

Access them in your code using process.env:

app/index.tsx
export default function Home() {
	const apiUrl = process.env.EXPO_PUBLIC_API_URL;
	const appName = process.env.EXPO_PUBLIC_APP_NAME;

	return (
		<View>
			<Text>Welcome to {appName}</Text>
			<Text>API: {apiUrl}</Text>
		</View>
	);
}

That’s it! Expo automatically loads and inlines these values at build time. You can even update them on the fly by now!


The Security Rule You Must Know

Here’s the critical part that trips up many developers:

EXPO_PUBLIC variables are NOT secret!

They’re embedded directly in your JavaScript bundle, which means anyone can read them. Even if you obfuscate your code, with enough criminal intent, they can be decrypted.

What NOT to Store

.env
# ❌ DANGEROUS - Never do this!
EXPO_PUBLIC_API_KEY=sk_live_abc123secretkey
EXPO_PUBLIC_DATABASE_PASSWORD=mypassword123
EXPO_PUBLIC_STRIPE_SECRET_KEY=sk_test_xyz789
EXPO_PUBLIC_OPENAI_API_KEY=sk-proj-abc123

Why is this bad? Anyone can:

  1. Download your app
  2. Extract the JavaScript bundle
  3. Search for EXPO_PUBLIC_ variables
  4. Steal your API keys and secrets

The damage: Unauthorized API usage, data breaches, massive bills, and security incidents.

What’s Safe to Store

.env
# ✅ Safe - Public information
EXPO_PUBLIC_API_URL=https://api.example.com
EXPO_PUBLIC_APP_VERSION=1.0.0
EXPO_PUBLIC_ENVIRONMENT=production
EXPO_PUBLIC_FEATURE_ANALYTICS=true
EXPO_PUBLIC_SENTRY_DSN=https://public@sentry.io/123

Rule of thumb: If it’s okay for users to see it, it’s safe for EXPO_PUBLIC_. If not, it doesn’t belong in your frontend code!


Safe Secrets: API Routes to the Rescue

For actual secrets, use API routes with Expo Router. Variables without EXPO_PUBLIC_ are safe here because they run server-side:

.env
# Server-side secrets (no EXPO_PUBLIC_ prefix)
OPENAI_API_KEY=sk-proj-abc123
DATABASE_URL=postgresql://user:pass@host/db
STRIPE_SECRET_KEY=sk_live_xyz789

# Client-side public values
EXPO_PUBLIC_API_URL=https://api.example.com

Use them in API routes:

app/api/generate+api.ts
export async function POST(request: Request) {
	// ✅ Safe - runs server-side only
	const apiKey = process.env.OPENAI_API_KEY;

	const response = await fetch('https://api.openai.com/v1/completions', {
		headers: {
			Authorization: `Bearer ${apiKey}`
		}
	});

	return Response.json(await response.json());
}

Your frontend calls this API route instead of calling OpenAI directly - keeping your key safe on the server!

app/index.tsx
export default function Home() {
	const generateText = async () => {
		// ✅ Calls your API route, not OpenAI directly
		const response = await fetch(`${process.env.EXPO_PUBLIC_API_URL}/api/generate`, {
			method: 'POST'
		});
		const data = await response.json();
	};

	return <Button title="Generate" onPress={generateText} />;
}

Result: Your OpenAI key never leaves the server. Users can’t steal it!


Multiple Environments

Expo supports standard .env file precedence for different environments:

terminal
.env                # Default for all environments
.env.local          # Local overrides (add to .gitignore!)
.env.development    # Development only
.env.production     # Production only

Example setup:

.env.development
EXPO_PUBLIC_API_URL=http://localhost:3000
EXPO_PUBLIC_ENVIRONMENT=development
.env.production
EXPO_PUBLIC_API_URL=https://api.myapp.com
EXPO_PUBLIC_ENVIRONMENT=production

Pro tip: Add .env.local to .gitignore so each developer can have their own local overrides!


EAS Build: Upload Your Secrets

When building with EAS, you can upload environment variables to EAS servers instead of committing them to your repo. This is perfect for team collaboration and CI/CD!

Set Environment Variables on EAS

terminal
# Create a secret on EAS
eas secret:create --name OPENAI_API_KEY --value sk-proj-abc123 --type string

# Or upload from your local .env
eas env:push

# Pull secrets from EAS for local development
eas env:pull

Configure in eas.json

eas.json
{
	"build": {
		"production": {
			"env": {
				"EXPO_PUBLIC_API_URL": "https://api.myapp.com",
				"EXPO_PUBLIC_ENVIRONMENT": "production"
			}
		},
		"development": {
			"env": {
				"EXPO_PUBLIC_API_URL": "https://staging.myapp.com",
				"EXPO_PUBLIC_ENVIRONMENT": "staging"
			}
		}
	}
}

Benefits:

  • Secrets never committed to git
  • Different values per build profile
  • Team members can share secrets via EAS
  • Automatic injection during EAS builds

Important: EAS secrets are still inlined if they use EXPO_PUBLIC_ prefix! Use them for build-time secrets or server-side API routes.


Wrapping Up

Environment variables in Expo are powerful but require careful handling. Remember the golden rule: EXPO_PUBLIC = Public Information.

Quick recap:

  1. Use .env files with EXPO_PUBLIC_ prefix for public config
  2. Never put secrets in EXPO_PUBLIC_ variables
  3. Use API routes for server-side secrets (no EXPO_PUBLIC_ prefix)
  4. Upload secrets to EAS for builds instead of committing them
  5. Use static references only: process.env.EXPO_PUBLIC_KEY

Want to dive deeper?

Found API keys in production code? It happens to the best of us - rotate them immediately and move to API routes!

Tomorrow we’ll explore Day 11’s topic - see you then! 🎄