// the apt way to read config
Typed config,
from any source.
Read config as real types from process.env, a Workers binding, a browser bundle, or any object you bind. Zero runtime dependencies. Zod/Valibot/Arktype validation.
import { Envapter, Converters } from 'envapt';
// numbers, with a fallback
const port = Envapter.getNumber('PORT', 3000);
// ordered keys, first defined wins
const url = Envapter.get(['DATABASE_URL', 'DB_URL']);
// typed lists
const cors = Envapter.getUsing('CORS',
Converters.array({ of: Converters.Url })
);Bind a source, read it typed.
A source is any object with a readVars() method. On Node, Bun, and Deno one binds itself on import, reading process.env and your .env files. On Cloudflare Workers, in the browser, or for secrets you fetch from a store at boot, you bind it in one line and read with the same typed API.
import { env } from 'cloudflare:workers';
import { Envapter, PortableSource } from 'envapt';
Envapter.useSource(new PortableSource(env));
const port = Envapter.getNumber('PORT', 3000);import { Envapter, PortableSource } from 'envapt';
// Vite replaces import.meta.env at build time
Envapter.useSource(new PortableSource(import.meta.env));
const beta = Envapter.getBoolean('VITE_BETA', false);Node · Bun · Deno bind the source on import.
Workers · Browser · Sources · Secret stores
Fetch secrets from 1Password, Vault, Doppler, or any store at boot, then bind the resolved object as a source. envapt reads them typed, it does not fetch them. Using secret stores.
Browser values are inlined into your bundle, so seed public configuration only, never a secret.
Two ways to read, one engine.
Read values with Envapter, or bind them to class fields with @Envapt. Both share the same parsing, converters, and cache.
import { Envapter, Converters } from 'envapt';
const config = {
port: Envapter.getNumber('PORT', 3000),
dbUrl: Envapter.getUsing('DATABASE_URL', Converters.Url)
};import { EnvNum } from 'envapt';
class Config {
@EnvNum('PORT', 3000)
static accessor port: number;
}A fallback removes undefined from the return type.
Every value, validated and typed.
Pick a converter and read the value back as its real type. All built in, zero validator dependencies.
Or pass your own (raw, fallback) => T function, or validate through a Standard Schema validator (zod, valibot, arktype). All converters.
The default Node source loads .env.
When the Node source is active, envapt reads .env.production.local, then .env.production, then .env.local, then .env in production. Values from higher files win, missing files are skipped. Bind another source and the cascade steps aside.
load order · most-specific wins
- .env.production.localwins
DATABASE_URL=pg://${DB_HOST}:5432/app - .env.production
DB_HOST=prod-db - .env.localskipped
- .envbase
resolved
Envapter.get('DATABASE_URL');
// pg://prod-db:5432/app${DB_HOST} expanded into the URL.
Node 20+ · Bun 1.3+ · Deno 2.5+ · zero dependencies
import 'envapt/config';Read config from any source, fully typed.
Zero runtime dependencies. Zod/Valibot/Arktype validation.