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.
| Runtime | Condition | Reads | Gotcha |
|---|---|---|---|
| Node | node | process.env and the .env cascade | — |
| Bun | node | process.env and the .env cascade | — |
| Deno | deno | process.env and the .env cascade | needs Deno 2.5+ |
| Deno Deploy | deno | dashboard vars via process.env / Deno.env | no .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).
| Runtime | Condition | Seed from | process.env | Gotcha |
|---|---|---|---|---|
| Cloudflare Workers | workerd | the env binding | no | bind once at module load |
| Vercel Edge | edge-light | process.env | yes | deprecated by Vercel, the Node runtime auto-binds |
| Fastly Compute | fastly | env() keys, no bulk object | no | add the fastly condition |
| Expo / React Native | react-native | Constants.expoConfig.extra | build-inlined | process.env is not a runtime object |
| Browser | browser | bundler-injected config | no | ships in the client bundle |
Engine versions
| Runtime | Minimum |
|---|---|
| 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 envaptThe 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
envapton Workers, edge runtimes (edge-light,fastly,worker), the browser, and react-native. It contains nonode:imports. Bind aPortableSourceand read with the same typed API. - The node build also resolves from bare
envapton Node, Bun, and Deno.FileSourceis bound on import, so it readsprocess.envand the.envcascade, 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.
{ "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.
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.