v9.1.0

Quick Start

Install envapt and read typed config on Node, Workers, or the browser, binding the platform's env object first outside Node, Bun, and Deno.

WARNING

envapt is ESM only. Node throws ERR_PACKAGE_PATH_NOT_EXPORTED on require('envapt'). In a CommonJS project, load it with await import('envapt') inside an async function. Compatibility lists the runtime versions.

Install

pnpm add envapt

Read a value

On Node, Bun, and Deno, envapt binds process.env as its source and loads your .env files when you import it, so you need no setup there. Workers and Vite hand config over as an object with no global to read, so on those and other edge runtimes envapt starts with no source. The Workers and Browser tabs bind that object with useSource before the first read. PortableSource copies the object when you create it. If you change the object later, reads still return the values it had when you created the source.

envapt loads the .env files from your working directory. This step reads CACHE_TTL from the file below. The startup check further down reads the other three keys.

.env
PORT=8080
DATABASE_URL=postgres://app:secret@localhost:5432/app
CACHE_TTL=10m
ALLOWED_ORIGINS=https://app.example.com,https://admin.example.com
src/config.ts
const cacheTtl = .('CACHE_TTL', ., '1m');
const cacheTtl: number

Through plain process.env, filled by your shell or dotenv, CACHE_TTL is the string '10m'. An empty CACHE_TTL= is '', which a ?? fallback doesn't replace. getAs turns 10m into 600000 milliseconds, and the '1m' fallback applies if the variable is missing, empty, or isn't a duration. Coming from dotenv? .env files on Node has the envapt/config import that replaces dotenv/config.

Reading values covers the other readers. Converters has the full set of converters, including URLs, durations, and arrays. To check a value against a schema from a library such as zod, valibot, or arktype, see Validating with a schema.

Check required config at startup

A fallback hides a bad value. If the app can't run without a setting, read it with getRequired, which throws when the value is missing or can't convert. For a group of settings, call getRequiredAll. If any keys are missing, it throws one MissingEnvValue error that lists them all. If every key is present but some values can't convert, it throws one InvalidEnvValue error that lists those keys.

src/config.ts
export const  = .(
    {
        : .,
        : .,
        : .()
    },
    'camelCase'
);

config;
const config: {
    allowedOrigins: string[];
    databaseUrl: URL;
    port: number;
}

The result is typed from the object you pass. The optional second argument, 'camelCase', renames each key. Without it, the keys stay as you wrote them. With it, a key like DATABASE_URL becomes config.databaseUrl. A key read with Converters.Url is a URL, and one read with Converters.array() is a string[] split on commas. Required values describes the other casings and the error codes.

Import config from your app's entry point. The module throws when it loads, so a missing or bad value stops the app at startup. If a value can't convert and nothing catches the error, Node prints the error's issues too. An issue can quote a secret, such as the password in DATABASE_URL. Required values shows the catch block that logs only the keys.

Prefer class fields? The @Envapt decorator declares the same settings on a class.

On this page