v8.1.0

Compatibility

Runtime support, engine versions, module formats, the portable and node builds, and how decorators behave across runtimes.

envapt runs on Node, Bun, and Deno with the same API and zero runtime dependencies. It also runs on Cloudflare Workers (workerd), other edge runtimes, and the browser, where you bind the source yourself (a PortableSource) and read with the same typed API. The functional reader API works on every runtime with no build step. The default @Envapt accessor decorators need no flag either, the legacy envapt/legacy decorators have per-runtime requirements covered below.

Binding by runtime

envapt splits into two builds, selected by the package exports conditions. On the node build a source binds on import. On the portable build you bind one yourself.

Node build, binds automatically

FileSource binds on import and reads process.env plus the .env cascade, so the first read works with no setup.

RuntimeConditionReadsGotcha
Nodenodeprocess.env and the .env cascade
Bunnodeprocess.env and the .env cascade
Denodenoprocess.env and the .env cascadeneeds Deno 2.5+
Deno Deploydenodashboard vars via process.env / Deno.envno .env files, set vars in the dashboard

Portable build, you bind a source

Bind a PortableSource from the object the platform gives you before your first read. An unbound read throws NoSourceBound, and the file-only APIs warn once and no-op (see fileApiMode below).

RuntimeConditionSeed fromprocess.envGotcha
Cloudflare Workersworkerdthe env bindingnobind once at module load
Vercel Edgeedge-lightprocess.envyesdeprecated by Vercel, the Node runtime auto-binds
Fastly Computefastlyenv() keys, no bulk objectnoadd the fastly condition
Expo / React Nativereact-nativeConstants.expoConfig.extrabuild-inlinedprocess.env is not a runtime object
Browserbrowserbundler-injected confignoships in the client bundle

Engine versions

RuntimeMinimum
Node>=20
Bun>=1.3
Deno>=2.5

Each release is tested against Node 20, 22, and 24, plus Bun and Deno.

Cloudflare Workers and the browser have no semver engine; the right build is selected by the package exports conditions (below), not by an engine version.

Install

pnpm add envapt

The Deno tab installs from JSR; you can also use the npm specifier with deno add npm:envapt.

Module formats

envapt ships both ESM and CommonJS with type definitions for each, so import and require both resolve. There are no runtime dependencies.

Portable and node builds

The package exports conditions select between two builds:

  • The portable build resolves from bare envapt on Workers, edge runtimes (edge-light, fastly, worker), the browser, and react-native. It contains no node: imports. Bind a PortableSource and read with the same typed API.
  • The node build also resolves from bare envapt on Node, Bun, and Deno. FileSource is bound on import, so it reads process.env and the .env cascade, and the file APIs work.

Bind a source before your first read on the portable build; see Sources.

Import from envapt on every runtime, no subpath import is needed. On the portable build, the file-only config APIs (envPaths, baseDir, envFileOptions, configureProfiles, resetProfiles) warn once and no-op by default. Set Envapter.fileApiMode = 'throw' to make them throw FileApiUnsupported instead. fileApiMode governs only the portable file-API behavior. On the node build a bare (non-filesystem) source still throws FileApiUnsupported when a file API is called. An unbound read throws NoSourceBound on all runtimes regardless of fileApiMode.

Deno and Deno Deploy resolve the node build by design. On Deno the .env cascade and file APIs work as on Node. Deno Deploy has no .env files on disk, so set variables in its dashboard and read them through process.env / Deno.env.

Decorators per runtime

The default @Envapt import is a TC39 (Stage 3) accessor decorator. It needs no experimentalDecorators flag and works on every runtime, including Bun and Deno running a .ts file directly.

The legacy decorators at envapt/legacy are the older TypeScript decorators, so they need experimentalDecorators. The functional API needs no flag on any runtime.

Node and Deno. The default accessor decorators work with no configuration. The legacy form needs the flag set in tsconfig.json on Node or deno.json on Deno.

tsconfig.json / deno.json
{ "compilerOptions": { "experimentalDecorators": true } }

Bun. The functional API and the default accessor decorators both work when you run a .ts file directly. Only the legacy form does not.

DANGER

Bun emits TC39 (Stage 3) decorators and ignores experimentalDecorators (bun#27575), so the legacy envapt/legacy decorators do not work when you run a .ts entry through Bun directly. The default accessor decorators on envapt are themselves Stage 3, so they work. To use the legacy form on Bun, precompile with tsc, tsdown, or Vite and run the compiled JavaScript.

On this page