Cross-platform environment variables loader and validator with Standard Schema support.
- 🚀 Cross-platform - Works on Node.js, Bun, and Deno
- ✅ Standard Schema - Compatible with Zod, Valibot, ArkType, and more
- 📁 .env file loading - With proper file priority support
- 🔌 Pluggable sources - Load env from JSON files, CLI commands, secret managers, or any custom source
- 🔒 Type-safe - Full TypeScript support with type inference
- 🪶 Lightweight - Minimal dependencies
npm install enwow
# or
pnpm add enwow
# or
yarn add enwowYou'll also need a schema library that supports Standard Schema:
npm install zod
# or
npm install valibot
# or any other Standard Schema compatible libraryimport { Env } from 'enwow'
import { z } from 'zod'
// Define your schema
const env = await Env.create({
PORT: z.string().transform(Number).default('3000'),
HOST: z.string().default('localhost'),
DATABASE_URL: z.string().url(),
DEBUG: z.string().optional(),
})
// Type-safe access
env.get('PORT') // number
env.get('HOST') // string
env.get('DATABASE_URL') // string
env.get('DEBUG') // string | undefinedimport { Env } from 'enwow'
import { z } from 'zod'
const env = await Env.create(new URL('./', import.meta.url), {
PORT: z.string().transform(Number).default('3000'),
HOST: z.string().default('localhost'),
})
// Values from .env files are loaded and validatedFiles are loaded in the following order (highest priority first):
| Priority | File Name | Environment | Notes |
|---|---|---|---|
| 1st | .env.[NODE_ENV].local |
Current environment | Loaded when NODE_ENV is set |
| 2nd | .env.local |
All | Not loaded in test environment |
| 3rd | .env.[NODE_ENV] |
Current environment | Loaded when NODE_ENV is set |
| 4th | .env |
All | Always loaded |
process.env always has the highest priority and will override values from any file.
Create a new Env instance without loading .env files.
const env = await Env.create({
PORT: z.string().transform(Number),
})Create a new Env instance and load .env files from the specified directory.
const env = await Env.create(new URL('./', import.meta.url), {
PORT: z.string().transform(Number),
})interface EnvCreateOptions {
// Ignore existing process.env values
ignoreProcessEnv?: boolean
// Override NODE_ENV for file loading
nodeEnv?: string
// Custom environment variables source
envSource?: Record<string, string | undefined>
// Custom source adapters (cannot be used with path argument)
sources?: EnvSourceAdapter[]
}Get a validated environment variable value.
const port = env.get('PORT') // Type: numberGet all validated environment variables as an object.
const all = env.all() // Type: { PORT: number, HOST: string, ... }Check if a variable is defined.
if (env.has('DEBUG')) {
// ...
}When validation fails, an EnvValidationError is thrown:
import { EnvValidationError } from 'enwow'
try {
const env = await Env.create({
REQUIRED_VAR: z.string(),
})
}
catch (error) {
if (error instanceof EnvValidationError) {
console.log('Validation failed:')
for (const issue of error.issues) {
console.log(` ${issue.path}: ${issue.message}`)
}
}
}By default, enwow loads environment variables from .env files and process.env. With source adapters, you can load from any source — JSON files, CLI commands, secret managers, or custom providers.
import { Env } from 'enwow'
import { fromFiles, fromJSON, fromObject, fromProcessEnv } from 'enwow/adapters'Sources are merged left-to-right: later sources override earlier ones.
| Adapter | Description |
|---|---|
fromFiles({ directory, ...options }) |
Load from .env files (same as the default behavior) |
fromProcessEnv() |
Read from process.env (cross-platform) |
fromJSON({ path }) |
Read from a JSON file with flat key-value structure |
fromObject({ env }) |
Use a static object (useful for testing and defaults) |
import { Env } from 'enwow'
import { fromFiles, fromJSON, fromProcessEnv } from 'enwow/adapters'
import { z } from 'zod'
const env = await Env.create({
PORT: z.string().transform(Number).default('3000'),
DATABASE_URL: z.string().url(),
API_SECRET: z.string(),
}, {
sources: [
fromFiles({ directory: new URL('./', import.meta.url) }), // lowest priority
fromJSON({ path: './config/env.json' }),
fromProcessEnv(), // highest priority
],
})Use defineSourceAdapter to create a type-safe adapter factory:
import { defineSourceAdapter, fromFiles, fromProcessEnv } from 'enwow/adapters'
interface VaultOptions {
url: string
token: string
}
const vault = defineSourceAdapter<VaultOptions>((opts) => {
return {
name: 'vault',
async load() {
const response = await fetch(opts.url, {
headers: { 'X-Vault-Token': opts.token },
})
const { data } = await response.json()
return data.data
},
}
})
const env = await Env.create(schema, {
sources: [
fromFiles({ directory: new URL('./', import.meta.url) }),
vault({ url: 'https://vault.example.com/v1/secret/data/myapp', token: process.env.VAULT_TOKEN! }),
fromProcessEnv(),
],
})Note: The
sourcesoption cannot be used together with thepathargument inEnv.create(path, schema). UsefromFiles()adapter instead.
import { Env } from 'enwow'
import * as v from 'valibot'
const env = await Env.create({
PORT: v.pipe(v.string(), v.transform(Number), v.minValue(1), v.maxValue(65535)),
HOST: v.optional(v.string(), 'localhost'),
})import { type } from 'arktype'
import { Env } from 'enwow'
const env = await Env.create({
PORT: type('string.integer').pipe(Number),
HOST: type('string').default('localhost'),
})import { EnvValidator, parseEnv } from 'enwow'
import { z } from 'zod'
const validator = new EnvValidator({
PORT: z.string().transform(Number),
})
const result = validator.validate(process.env)import { EnvLoader, EnvParser } from 'enwow'
const loader = new EnvLoader(new URL('./', import.meta.url))
const files = await loader.load()
for (const file of files) {
const parser = new EnvParser(file.contents)
const parsed = parser.parse()
console.log(parsed)
}Local development
MIT License