Skip to main content

Custom env files

Env Core supports loading environment variables from custom .env files. This is useful for managing different configurations for development, testing, staging, and production environments.

Basic custom file usage

Pass the filename as the second argument to validateEnv:
import { validateEnv } from 'env-core';
import { envSchema } from './envSchema.js';

// Load from default .env file
const env = validateEnv(envSchema);

// Load from custom file
const env = validateEnv(envSchema, 'test.env');
The file path is relative to your project root (where you run node).

Environment-specific files

Use different files for different environments:
const envFile = process.env.NODE_ENV === 'test' ? 'test.env' : '.env';
const env = validateEnv(envSchema, envFile);
Or use a more comprehensive approach:
const getEnvFile = () => {
  switch (process.env.NODE_ENV) {
    case 'test':
      return 'test.env';
    case 'staging':
      return 'staging.env';
    case 'production':
      return 'production.env';
    default:
      return '.env';
  }
};

const env = validateEnv(envSchema, getEnvFile());

Testing setup

Create a dedicated test environment file:
test.env
PORT=4000
NODE_ENV=test
DEBUG=false
DATABASE_URL=postgresql://localhost/myapp_test
REDIS_URL=redis://localhost:6379/1
test/setup.js
import { validateEnv } from 'env-core';
import { envSchema } from '../src/envSchema.js';

// Use test environment for all tests
export const testEnv = validateEnv(envSchema, 'test.env');
test/integration.test.js
import { testEnv } from './setup.js';
import { startServer } from '../src/server.js';

describe('Integration tests', () => {
  it('should start server with test config', async () => {
    const server = await startServer(testEnv.PORT);
    expect(server).toBeDefined();
  });
});

Multiple environment files

Maintain separate files for each environment:
project/
├── .env              # Development (default)
├── .env.example      # Template for developers
├── test.env          # Testing
├── staging.env       # Staging environment
└── production.env    # Production (if needed locally)
Never commit production secrets to version control! Use .gitignore to exclude sensitive .env files.

.env.example template

Maintain a .env.example file (safe to commit) showing required variables:
.env.example
# Server configuration
PORT=3000
HOST=0.0.0.0
NODE_ENV=development

# Database
DATABASE_URL=postgresql://localhost/myapp
DB_POOL_SIZE=10

# Features
DEBUG=true
ENABLE_LOGGING=true

# External services
API_KEY=your_api_key_here
API_URL=https://api.example.com
Developers can copy this to .env and fill in real values:
cp .env.example .env

NestJS with custom files

For NestJS, you need to load the file before passing to ConfigModule:
src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { validateEnv } from 'env-core';
import { envSchema } from './envSchema';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: process.env.NODE_ENV === 'test' ? 'test.env' : '.env',
      validate: (config) => validateEnv(envSchema, config),
    }),
  ],
})
export class AppModule {}

Express.js with custom files

For Express.js, specify the file directly:
src/index.js
import express from 'express';
import { validateEnv } from 'env-core';
import { envSchema } from './envSchema.js';

// Determine which env file to use
const envFile = process.env.NODE_ENV === 'test' ? 'test.env' : '.env';
const env = validateEnv(envSchema, envFile);

const app = express();

app.listen(env.PORT, () => {
  console.log(`Server running on port ${env.PORT}`);
});

File loading behavior

Understand how Env Core loads environment files:

File exists

If the specified file exists, Env Core loads it and merges with process.env:
// test.env exists
const env = validateEnv(envSchema, 'test.env');
// Variables from test.env are loaded and merged with process.env

File doesn’t exist

If the file doesn’t exist, Env Core uses process.env only:
// custom.env doesn't exist
const env = validateEnv(envSchema, 'custom.env');
// Falls back to process.env (no error)
Missing .env files don’t cause errors - Env Core silently falls back to process.env. Validation errors only occur if required variables are missing.

Practical examples

Development and production

.env.development
PORT=3000
NODE_ENV=development
DEBUG=true
DATABASE_URL=postgresql://localhost/myapp_dev
LOG_LEVEL=debug
.env.production
PORT=8080
NODE_ENV=production
DEBUG=false
DATABASE_URL=postgresql://prodhost/myapp
LOG_LEVEL=info
const isProd = process.env.NODE_ENV === 'production';
const envFile = isProd ? '.env.production' : '.env.development';
const env = validateEnv(envSchema, envFile);

Testing with different databases

test.env
DATABASE_URL=postgresql://localhost/myapp_test
REDIS_URL=redis://localhost:6379/1
DEBUG=false
NODE_ENV=test
test.integration.env
DATABASE_URL=postgresql://testhost/integration_db
REDIS_URL=redis://testhost:6379/1
DEBUG=true
NODE_ENV=test
const testType = process.env.TEST_TYPE || 'unit';
const envFile = testType === 'integration' ? 'test.integration.env' : 'test.env';
const env = validateEnv(envSchema, envFile);

Local overrides

project/
├── .env              # Committed defaults
├── .env.local        # Local overrides (gitignored)
import fs from 'fs';

// Prefer local overrides if they exist
const envFile = fs.existsSync('.env.local') ? '.env.local' : '.env';
const env = validateEnv(envSchema, envFile);

.gitignore configuration

Protect sensitive files:
.gitignore
# Environment files
.env
.env.local
.env.*.local
production.env
staging.env

# Keep these
!.env.example
!test.env
Test environment files can often be committed since they use local test databases and dummy credentials.

CI/CD integration

In CI/CD pipelines, set environment variables directly instead of using files:
.github/workflows/test.yml
name: Test
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    env:
      PORT: 4000
      NODE_ENV: test
      DATABASE_URL: postgresql://localhost/test_db
      DEBUG: false
    steps:
      - uses: actions/checkout@v2
      - run: npm test
Env Core validates process.env if the file doesn’t exist:
// In CI, test.env might not exist
// Env Core will validate process.env instead
const env = validateEnv(envSchema, 'test.env');

Best practices

Maintain an example file showing all required variables:
# Always commit this
.env.example

# Developers run:
cp .env.example .env
Commit test.env with safe test values so all developers and CI use the same test configuration:
test.env
DATABASE_URL=postgresql://localhost/myapp_test
NODE_ENV=test
Add production env files to .gitignore:
.gitignore
.env
.env.production
.env.staging
*.env.local
Add instructions in your README:
## Setup

1. Copy .env.example to .env
2. Fill in your local values
3. For testing, test.env is already configured

Next steps

Simple validation

Learn basic validation patterns

Advanced configuration

Explore defaults and optional values

Build docs developers (and LLMs) love