Documentation Index
Fetch the complete documentation index at: https://mintlify.com/zhcndoc/bun/llms.txt
Use this file to discover all available pages before exploring further.
Bun automatically loads environment variables from .env files, making configuration management seamless across development, testing, and production environments.
Quick Start
Create a .env file in your project root:
DATABASE_URL=postgresql://localhost/mydb
API_KEY=secret_key_123
PORT=3000
Access variables in your code:
Access environment variables
console.log(process.env.DATABASE_URL);
console.log(process.env.API_KEY);
console.log(process.env.PORT);
// Or use Bun.env
console.log(Bun.env.DATABASE_URL);
No configuration needed - Bun loads .env automatically!
.env File Priority
Bun loads multiple .env files in priority order:
Process Environment
Variables already set in the shell (highest priority)
.env.local
Local overrides (gitignored by convention)
.env.[environment].local
Environment-specific local overrides:
.env.development.local
.env.production.local
.env.test.local
.env.[environment]
Environment-specific configuration:
.env.development
.env.production
.env.test
.env
Base configuration (lowest priority)
Environment detection:
NODE_ENV=production → loads .env.production
NODE_ENV=development → loads .env.development (default)
NODE_ENV=test → loads .env.test
Implementation: src/env_loader.zig:1-18 - .env file detection
Standard .env syntax:
# Comments start with #
BASIC=value
# Quotes (optional for simple values)
QUOTED="value with spaces"
SINGLE='value with spaces'
# Multiline values
MULTILINE="line 1
line 2
line 3"
# Variable expansion
BASE_URL=https://api.example.com
API_ENDPOINT=${BASE_URL}/v1
# Empty values
EMPTY=
EMPTY_QUOTED=""
# Special characters
PASSWORD="p@ssw0rd!#$%"
# Export syntax (ignored by Bun)
export EXPORTED=value
Escaping
Escaping special characters
# Escape quotes
QUOTE="He said \"hello\""
# Escape newlines
NEWLINE="Line 1\nLine 2"
# Escape dollar signs
LITERAL_DOLLAR="Price is \$100"
# Backslashes
PATH="C:\\Users\\Name"
Accessing Variables
process.env
Node.js-compatible API:
// Read variables
const dbUrl = process.env.DATABASE_URL;
const port = process.env.PORT || "3000";
// Check if variable exists
if (process.env.API_KEY) {
console.log("API key configured");
}
// Set variables (runtime only)
process.env.DYNAMIC_VAR = "value";
// Delete variables
delete process.env.TEMP_VAR;
// Iterate all variables
for (const [key, value] of Object.entries(process.env)) {
console.log(`${key}=${value}`);
}
Bun.env
Bun-specific API (read-only):
// Read variables (same as process.env)
const dbUrl = Bun.env.DATABASE_URL;
const apiKey = Bun.env.API_KEY;
// Type-safe access
console.log(Bun.env.PORT); // string | undefined
// Cannot modify
// Bun.env.NEW_VAR = "value"; // Error: read-only
Differences:
Bun.env is read-only
process.env allows runtime modifications
- Both reference the same underlying data
Custom .env Files
Load specific .env files:
CLI Flag
# Load specific file
bun --env-file=.env.staging run app.ts
# Load multiple files (last wins)
bun --env-file=.env --env-file=.env.local run app.ts
bunfig.toml
[env]
# Disable default .env loading
file = false
# Or specify custom files
file = [".env", ".env.custom"]
Implementation: src/bunfig.zig:151-186 - Env config parsing
Programmatic Loading
import { file } from "bun";
// Read and parse .env file
const envFile = await file(".env.production").text();
const lines = envFile.split("\n");
for (const line of lines) {
const match = line.match(/^([^=]+)=(.*)$/);
if (match) {
const [, key, value] = match;
process.env[key] = value.replace(/^["']|["']$/g, "");
}
}
Environment-Specific Configuration
Development
NODE_ENV=development
DATABASE_URL=postgresql://localhost/dev
LOG_LEVEL=debug
ENABLE_HOT_RELOAD=true
Production
NODE_ENV=production
DATABASE_URL=postgresql://prod.example.com/db
LOG_LEVEL=error
ENABLE_HOT_RELOAD=false
Testing
NODE_ENV=test
DATABASE_URL=postgresql://localhost/test
LOG_LEVEL=silent
MOCK_API=true
Local Overrides
# Override for local development
DATABASE_URL=postgresql://localhost/myusername_dev
API_KEY=my_personal_test_key
DEBUG=true
Add to .gitignore:
Type Safety
Declare Types
declare global {
namespace NodeJS {
interface ProcessEnv {
DATABASE_URL: string;
API_KEY: string;
PORT?: string;
NODE_ENV: "development" | "production" | "test";
}
}
}
export {};
Validate Variables
function getEnv(key: string): string {
const value = process.env[key];
if (!value) {
throw new Error(`Missing environment variable: ${key}`);
}
return value;
}
// Required variables
const DATABASE_URL = getEnv("DATABASE_URL");
const API_KEY = getEnv("API_KEY");
// Optional with default
const PORT = process.env.PORT || "3000";
Zod Schema
import { z } from "zod";
const envSchema = z.object({
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(32),
PORT: z.string().regex(/^\d+$/).transform(Number).default("3000"),
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
});
const env = envSchema.parse(process.env);
export default env;
Special Variables
NODE_ENV
Determines which .env.[environment] file loads:
// Check environment
const isDev = process.env.NODE_ENV === "development";
const isProd = process.env.NODE_ENV === "production";
const isTest = process.env.NODE_ENV === "test";
// Conditional behavior
if (isDev) {
console.log("Development mode");
}
PATH
System executable search paths:
// Current PATH
console.log(process.env.PATH);
// Add to PATH (runtime)
process.env.PATH = `/usr/local/bin:${process.env.PATH}`;
HOME / USERPROFILE
User home directory:
const home = process.env.HOME || process.env.USERPROFILE;
console.log(`User home: ${home}`);
Proxy Variables
HTTP/HTTPS proxy configuration:
HTTP_PROXY=http://proxy.example.com:8080
HTTPS_PROXY=http://proxy.example.com:8080
NO_PROXY=localhost,127.0.0.1,.local
Implementation: src/env_loader.zig:167-198 - HTTP proxy detection
TLS Configuration
# Disable certificate validation (development only!)
NODE_TLS_REJECT_UNAUTHORIZED=0
Implementation: src/env_loader.zig:144-161 - TLS reject unauthorized
Security Best Practices
Never commit .env files containing secrets to version control!
1. Gitignore .env Files
# Environment variables
.env
.env.local
.env.*.local
# Keep example file
!.env.example
2. Use .env.example
Commit a template without secrets:
DATABASE_URL=postgresql://localhost/mydb
API_KEY=
PORT=3000
NODE_ENV=development
3. Validate Required Variables
const required = ["DATABASE_URL", "API_KEY"];
for (const key of required) {
if (!process.env[key]) {
console.error(`Missing required environment variable: ${key}`);
process.exit(1);
}
}
4. Restrict Permissions
# Make .env readable only by owner
chmod 600 .env
5. Use Secrets Management
For production, use proper secrets management:
- AWS Secrets Manager
- HashiCorp Vault
- Azure Key Vault
- Google Secret Manager
Fetch from secrets manager
import { SecretsManager } from "@aws-sdk/client-secrets-manager";
const client = new SecretsManager({ region: "us-east-1" });
const secret = await client.getSecretValue({ SecretId: "prod/api-key" });
process.env.API_KEY = JSON.parse(secret.SecretString).apiKey;
Environment Loading Implementation
Bun’s environment variable loading:
Loader structure (src/env_loader.zig:7-29)
pub const Loader = struct {
map: *Map,
allocator: std.mem.Allocator,
@".env.local": ?logger.Source = null,
@".env.development": ?logger.Source = null,
@".env.production": ?logger.Source = null,
@".env.test": ?logger.Source = null,
@".env.development.local": ?logger.Source = null,
@".env.production.local": ?logger.Source = null,
@".env.test.local": ?logger.Source = null,
@".env": ?logger.Source = null,
custom_files_loaded: bun.StringArrayHashMap(logger.Source),
quiet: bool = false,
};
Loading order:
- Check each environment file
- Parse key=value pairs
- Expand variables (
${VAR})
- Merge into environment (lower priority files don’t override)
Advanced Usage
Environment Detection
class Config {
static isProduction() {
return process.env.NODE_ENV === "production";
}
static isDevelopment() {
return !process.env.NODE_ENV || process.env.NODE_ENV === "development";
}
static isTest() {
return process.env.NODE_ENV === "test";
}
static isCi() {
return !!(
process.env.CI ||
process.env.GITHUB_ACTIONS ||
process.env.GITLAB_CI ||
process.env.CIRCLECI
);
}
}
Implementation: src/env_loader.zig:70-76 - CI detection
Dynamic Configuration
interface AppConfig {
database: {
url: string;
pool: { min: number; max: number };
};
api: {
url: string;
key: string;
};
}
function loadConfig(): AppConfig {
const env = process.env.NODE_ENV || "development";
return {
database: {
url: process.env.DATABASE_URL!,
pool: {
min: Number(process.env.DB_POOL_MIN || 2),
max: Number(process.env.DB_POOL_MAX || 10),
},
},
api: {
url: process.env.API_URL!,
key: process.env.API_KEY!,
},
};
}
const config = loadConfig();
Variable Expansion
BASE_URL=https://api.example.com
API_V1=${BASE_URL}/v1
API_V2=${BASE_URL}/v2
FULL_ENDPOINT=${API_V1}/users
# Results in:
# API_V1=https://api.example.com/v1
# API_V2=https://api.example.com/v2
# FULL_ENDPOINT=https://api.example.com/v1/users
Debugging
Print All Variables
console.log(process.env);
// Or formatted
for (const [key, value] of Object.entries(process.env)) {
console.log(`${key}=${value}`);
}
Check Variable Loading
BUN_DEBUG_QUIET_LOGS=0 bun run app.ts 2>&1 | grep -i env
Verify .env Parsing
const content = await Bun.file(".env").text();
console.log("Raw .env content:");
console.log(content);
console.log("\nParsed variables:");
for (const line of content.split("\n")) {
if (line && !line.startsWith("#")) {
console.log(line);
}
}
Troubleshooting
Variables Not Loading
- Check file location:
.env must be in project root
- Check file name: Must be exactly
.env (no spaces)
- Check syntax:
KEY=value (no spaces around =)
- Check NODE_ENV: Correct environment file loaded?
Variables Undefined
if (!process.env.API_KEY) {
console.error("API_KEY not found in environment");
console.log("Available keys:", Object.keys(process.env));
}
Priority Issues
Check which file sets the variable: