createEnv() validates environment variables at startup using a @vertz/schema schema. If any variable is missing or invalid, the server fails immediately with a clear error — not 30 minutes later when a request hits an undefined config value.
Why not process.env?
process.env.PORT is string | undefined. A typo in the variable name is a silent undefined. A missing .env file is a runtime surprise. There’s no type safety, no validation, and no way for TypeScript to help you.
createEnv() catches both issues at startup:
Basic usage
Loading .env files
Use the load property to read variables from dotenv files:
.env and developer-specific overrides in .env.local.
Precedence
| Source | Priority | Use case |
|---|---|---|
process.env | Lowest | CI/CD pipelines, system-level env |
.env files (in load order) | Medium | Project defaults, local overrides |
Explicit env record | Highest | Edge runtimes, tests |
process.env. If .env sets PORT=4000 and process.env.PORT is 3000, the result is 4000. This means your .env files are the source of truth for local development.
Missing files
Missing files are silently skipped. This is intentional —.env.local often doesn’t exist in CI, and that shouldn’t break your build:
Common file patterns
.env.local to .gitignore — it’s for secrets that shouldn’t be committed.
How it works
- Loads variables from
.envfiles (ifloadis specified) - Merges with
process.env(files override process env) - Applies explicit
envrecord on top (if provided) - Validates every variable against the schema
- Returns a frozen, immutable object — typed from the schema
- Throws at startup if validation fails
Startup error example
IfDATABASE_URL is missing and has no default:
Coercion
Environment variables are always strings. Uses.coerce.* to parse them into the right types:
Edge runtimes
On Cloudflare Workers, environment variables are passed as context bindings — not available inprocess.env. Pass them explicitly:
Testing
Inject controlled values for deterministic tests:Immutability
The returned object is deeply frozen. Attempts to mutate it throw at runtime:Pattern: single env module
Define oneenv.ts file per service and import it everywhere. Don’t scatter createEnv() calls across multiple files: