Skip to main content

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:
.env
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:
1

Process Environment

Variables already set in the shell (highest priority)
2

.env.local

Local overrides (gitignored by convention)
3

.env.[environment].local

Environment-specific local overrides:
  • .env.development.local
  • .env.production.local
  • .env.test.local
4

.env.[environment]

Environment-specific configuration:
  • .env.development
  • .env.production
  • .env.test
5

.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

.env File Format

Standard .env syntax:
.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:
process.env
// 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):
Bun.env
// 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

Custom env file
# 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

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

Manual 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

.env.development
NODE_ENV=development
DATABASE_URL=postgresql://localhost/dev
LOG_LEVEL=debug
ENABLE_HOT_RELOAD=true

Production

.env.production
NODE_ENV=production
DATABASE_URL=postgresql://prod.example.com/db
LOG_LEVEL=error
ENABLE_HOT_RELOAD=false

Testing

.env.test
NODE_ENV=test
DATABASE_URL=postgresql://localhost/test
LOG_LEVEL=silent
MOCK_API=true

Local Overrides

.env.local (gitignored)
# Override for local development
DATABASE_URL=postgresql://localhost/myusername_dev
API_KEY=my_personal_test_key
DEBUG=true
Add to .gitignore:
.gitignore
.env.local
.env.*.local

Type Safety

Declare Types

env.d.ts
declare global {
  namespace NodeJS {
    interface ProcessEnv {
      DATABASE_URL: string;
      API_KEY: string;
      PORT?: string;
      NODE_ENV: "development" | "production" | "test";
    }
  }
}

export {};

Validate Variables

Validation
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

Zod validation
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:
NODE_ENV
// 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:
PATH
// 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:
Home directory
const home = process.env.HOME || process.env.USERPROFILE;
console.log(`User home: ${home}`);

Proxy Variables

HTTP/HTTPS proxy configuration:
Proxy variables
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

TLS variables
# 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

.gitignore
# Environment variables
.env
.env.local
.env.*.local

# Keep example file
!.env.example

2. Use .env.example

Commit a template without secrets:
.env.example
DATABASE_URL=postgresql://localhost/mydb
API_KEY=
PORT=3000
NODE_ENV=development

3. Validate Required Variables

Startup validation
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

File 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:
  1. Check each environment file
  2. Parse key=value pairs
  3. Expand variables (${VAR})
  4. Merge into environment (lower priority files don’t override)

Advanced Usage

Environment Detection

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

Dynamic config
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

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 environment
console.log(process.env);

// Or formatted
for (const [key, value] of Object.entries(process.env)) {
  console.log(`${key}=${value}`);
}

Check Variable Loading

Debug env loading
BUN_DEBUG_QUIET_LOGS=0 bun run app.ts 2>&1 | grep -i env

Verify .env Parsing

Test .env file
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

  1. Check file location: .env must be in project root
  2. Check file name: Must be exactly .env (no spaces)
  3. Check syntax: KEY=value (no spaces around =)
  4. Check NODE_ENV: Correct environment file loaded?

Variables Undefined

Check if loaded
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:
Check all .env files
grep "API_KEY" .env*

Build docs developers (and LLMs) love