Environment variables allow you to configure your Astro application without hardcoding values. Use them for API keys, database URLs, feature flags, and other configuration that changes between environments.
Using Environment Variables
Access environment variables through import.meta.env:
---
const apiKey = import . meta . env . PUBLIC_API_KEY ;
const databaseUrl = import . meta . env . DATABASE_URL ;
---
< html >
< body >
< h1 > My Site </ h1 >
</ body >
</ html >
Only variables prefixed with PUBLIC_ are available in client-side code. Server-only variables are accessible only during SSR.
.env Files
Store environment variables in .env files in your project root:
# Public variables (exposed to client)
PUBLIC_API_URL = https://api.example.com
PUBLIC_SITE_NAME = My Awesome Site
# Private variables (server-only)
DATABASE_URL = postgresql://localhost:5432/mydb
API_SECRET_KEY = super-secret-key-do-not-share
EMAIL_PASSWORD = another-secret
Add .env files to .gitignore to avoid committing secrets to version control.
.env File Priority
Astro loads environment variables from multiple files in this order (highest priority first):
.env.production.local
Production environment, local overrides (gitignored)
.env.production
Production environment
.env.local
All environments, local overrides (gitignored)
# .env - Default values for all environments
PUBLIC_API_URL = https://api.example.com
# .env.local - Local development overrides (gitignored)
PUBLIC_API_URL = http://localhost:3000
# .env.production - Production values
PUBLIC_API_URL = https://api.production.com
DATABASE_URL = postgresql://prod-server/db
Public vs Private Variables
Public Variables
Private Variables
Variables prefixed with PUBLIC_ are available everywhere: PUBLIC_API_URL = https://api.example.com
PUBLIC_ANALYTICS_ID = GA-123456
Accessible in any file: src/components/Analytics.astro
< script >
// Available in client-side code
const analyticsId = import . meta . env . PUBLIC_ANALYTICS_ID ;
console . log ( 'Analytics ID:' , analyticsId );
</ script >
Public variables are embedded in client bundles. Never use PUBLIC_ for secrets!
Variables without PUBLIC_ are server-only: DATABASE_URL = postgresql://localhost/db
API_SECRET = secret-key
Only accessible in server code: ---
// Available in server-side code
const dbUrl = import . meta . env . DATABASE_URL ;
const db = await connectToDatabase ( dbUrl );
---
< script >
// ❌ Undefined in client code!
console . log ( import . meta . env . DATABASE_URL ); // undefined
</ script >
Type Safety
Type your environment variables for better IntelliSense and type checking:
/// < reference types = "astro/client" />
interface ImportMetaEnv {
readonly DATABASE_URL : string ;
readonly API_SECRET_KEY : string ;
readonly PUBLIC_API_URL : string ;
readonly PUBLIC_SITE_NAME : string ;
}
interface ImportMeta {
readonly env : ImportMetaEnv ;
}
Now TypeScript will:
Autocomplete environment variable names
Show type errors for missing variables
Catch typos at compile time
Make required variables non-optional in the type definition to catch missing values early.
Astro Environment Schema
For advanced type safety and validation, use Astro’s environment schema:
import { defineConfig , envField } from 'astro/config' ;
export default defineConfig ({
env: {
schema: {
// Public variables
PUBLIC_API_URL: envField . string ({
context: 'client' ,
access: 'public' ,
url: true
}),
PUBLIC_SITE_NAME: envField . string ({
context: 'client' ,
access: 'public' ,
default: 'My Site'
}),
// Server-only variables
DATABASE_URL: envField . string ({
context: 'server' ,
access: 'secret' ,
url: true
}),
API_PORT: envField . number ({
context: 'server' ,
access: 'public' ,
default: 3000 ,
min: 1 ,
max: 65535
}),
ENABLE_ANALYTICS: envField . boolean ({
context: 'server' ,
access: 'public' ,
default: false
}),
LOG_LEVEL: envField . enum ({
context: 'server' ,
access: 'public' ,
values: [ 'debug' , 'info' , 'warn' , 'error' ],
default: 'info'
})
}
}
}) ;
Field Types
envField . string ({
context: 'server' ,
access: 'secret' ,
default: 'default value' ,
min: 5 , // Minimum length
max: 100 , // Maximum length
length: 10 , // Exact length
url: true , // Must be a valid URL
includes: 'test' , // Must include substring
startsWith: 'https://' ,
endsWith: '.com'
})
envField . number ({
context: 'server' ,
access: 'public' ,
default: 0 ,
min: 0 , // Minimum value
max: 100 , // Maximum value
gt: 0 , // Greater than
lt: 100 , // Less than
int: true // Must be integer
})
envField . boolean ({
context: 'server' ,
access: 'public' ,
default: false
})
envField . enum ({
context: 'server' ,
access: 'public' ,
values: [ 'development' , 'staging' , 'production' ],
default: 'development'
})
Access Levels
context
'client' | 'server'
required
Where the variable can be accessed
access
'public' | 'secret'
required
Whether the variable is public or contains secrets
Secret client variables are not allowed for security reasons. Secrets must be server-only.
Runtime Access
Access validated environment variables at runtime:
import { getEnv } from 'astro:env' ;
// Type-safe access
const apiUrl = getEnv ( 'PUBLIC_API_URL' ); // string
const port = getEnv ( 'API_PORT' ); // number
const analytics = getEnv ( 'ENABLE_ANALYTICS' ); // boolean
const logLevel = getEnv ( 'LOG_LEVEL' ); // 'debug' | 'info' | 'warn' | 'error'
Default Values
Provide fallback values for optional variables:
src/components/Config.astro
---
const apiUrl = import . meta . env . PUBLIC_API_URL ?? 'https://api.example.com' ;
const maxItems = parseInt ( import . meta . env . PUBLIC_MAX_ITEMS ?? '10' );
const debugMode = import . meta . env . PUBLIC_DEBUG === 'true' ;
---
Or use the schema’s default values:
export default defineConfig ({
env: {
schema: {
PUBLIC_MAX_ITEMS: envField . number ({
context: 'client' ,
access: 'public' ,
default: 10
})
}
}
}) ;
Common Patterns
API Configuration
Database Connection
Feature Flags
PUBLIC_API_URL = https://api.example.com
API_KEY = secret-key-here
API_TIMEOUT = 5000
const API_URL = import . meta . env . PUBLIC_API_URL ;
const API_KEY = import . meta . env . API_KEY ;
const TIMEOUT = parseInt ( import . meta . env . API_TIMEOUT ?? '3000' );
export async function fetchData () {
const response = await fetch ( ` ${ API_URL } /data` , {
headers: {
'Authorization' : `Bearer ${ API_KEY } `
},
signal: AbortSignal . timeout ( TIMEOUT )
});
return response . json ();
}
DATABASE_URL = postgresql://user:password@localhost:5432/mydb
DATABASE_POOL_SIZE = 10
import { Pool } from 'pg' ;
const pool = new Pool ({
connectionString: import . meta . env . DATABASE_URL ,
max: parseInt ( import . meta . env . DATABASE_POOL_SIZE ?? '10' )
});
export const db = {
query : ( text : string , params ?: any []) => pool . query ( text , params )
};
PUBLIC_ENABLE_BETA_FEATURES = false
PUBLIC_ENABLE_ANALYTICS = true
PUBLIC_MAINTENANCE_MODE = false
---
const betaFeatures = import . meta . env . PUBLIC_ENABLE_BETA_FEATURES === 'true' ;
const analytics = import . meta . env . PUBLIC_ENABLE_ANALYTICS === 'true' ;
const maintenance = import . meta . env . PUBLIC_MAINTENANCE_MODE === 'true' ;
if ( maintenance ) {
return Astro . redirect ( '/maintenance' );
}
---
< html >
< body >
{ betaFeatures && < BetaFeatures /> }
{ analytics && < Analytics /> }
</ body >
</ html >
Loading .env in Scripts
Load environment variables in Node.js scripts:
import { loadEnv } from 'vite' ;
const env = loadEnv ( 'development' , process . cwd (), '' );
const databaseUrl = env . DATABASE_URL ;
console . log ( 'Connecting to:' , databaseUrl );
// Seed database...
Many hosting platforms provide their own environment variables:
Vercel
Netlify
Cloudflare Pages
VERCEL_ENV = production
VERCEL_URL = mysite.vercel.app
VERCEL_GIT_COMMIT_SHA = abc123
Access them like any other environment variable:
---
const isProd = import . meta . env . VERCEL_ENV === 'production' ;
const commitSha = import . meta . env . VERCEL_GIT_COMMIT_SHA ;
---
Built-in Variables
Astro provides several built-in variables:
MODE
'development' | 'production'
Current mode (astro dev vs astro build)
Whether running in production
Whether running in development
The site URL from your config
The base path from your config
---
const isDev = import . meta . env . DEV ;
const siteUrl = import . meta . env . SITE ;
---
{ isDev && < DevTools /> }
< link rel = "canonical" href = { ` ${ siteUrl }${ Astro . url . pathname } ` } />
Security Best Practices
Never commit secrets
Add .env and .env.local to .gitignore: .env
.env.local
.env.*.local
Use PUBLIC_ carefully
Only use PUBLIC_ for values safe to expose:
✅ API endpoints
✅ Public IDs
❌ API keys
❌ Passwords
❌ Secrets
Provide example file
Create .env.example with dummy values: DATABASE_URL = postgresql://localhost:5432/mydb
API_SECRET_KEY = your-secret-key-here
PUBLIC_API_URL = https://api.example.com
Validate on startup
Check required variables exist: const required = [ 'DATABASE_URL' , 'API_SECRET_KEY' ];
for ( const key of required ) {
if ( ! import . meta . env [ key ]) {
throw new Error ( `Missing required env var: ${ key } ` );
}
}
Configuration Astro configuration options
TypeScript TypeScript setup and types
Deployment Deploy your Astro site