Cloudflare Workers
Read typed config from a Worker's vars and secrets by binding the Cloudflare env object once.
Every vars entry and secret reaches a Worker as a property on env. If a handler reads env.ORIGIN_URL directly, it gets text and has to parse it into a URL itself. A JSON vars entry arrives as an object, and your code never checks its shape. Binding env to envapt once gives you typed values instead.
The portable build that a Worker loads reads nothing until you bind a source. Wrangler resolves envapt's portable build through the workerd export condition, so the import itself needs no setup.
Bind env in a config module
cloudflare:workers exports env, and you can read it at module scope. Put the bind and the reads in one config.ts. Every module that imports config reads after the bind. A module that calls Envapter.get directly can run before the bind and throw NoSourceBound, so read through config everywhere.
import { } from 'cloudflare:workers';
import { , , } from 'envapt';
.(new ());
const = .(
{
: .,
: .
},
'camelCase'
);
export const = {
...,
: .('CACHE_TTL', ., '1m')
};ORIGIN_URL is a plain vars entry and API_TOKEN is a secret, but both are properties of env, so one getRequiredAll call reads them. The 'camelCase' argument turns them into config.originUrl, a URL, and config.apiToken. cacheTtl is optional, so it falls back to one minute when CACHE_TTL is unset.
import { } from './config';
export default {
async (: ): <> {
if (..('authorization') !== `Bearer ${.}`) {
return new ('Unauthorized', { : 401 });
}
const { , } = new (.);
return (new ( + , .), );
}
} satisfies ;The handler only imports config, so no request converts or validates an env value. The satisfies ExportedHandler line and the cloudflare:workers import need Cloudflare's types, or TypeScript can't resolve them. Run wrangler types to generate them, or install @cloudflare/workers-types and add it to types in tsconfig.json.
This shape has two costs. If API_TOKEN is unset, the config module throws MissingEnvValue and wrangler dev stops with this error:
Uncaught Error: EnvaptError [305]: Missing required environment variables: API_TOKEN.The same throw can fail a deploy that goes out before the secret exists, because Cloudflare runs a Worker's top-level code when you deploy it and reports a throw there as a validation error (code 10021). Required values shows how to read the error. The second cost is that a running instance can keep the values it bound after a deploy, which Reading fresh values covers.
useSource sets state for the whole Worker instance, the isolate that serves many requests, so every request it serves shares the bound source. Keep per-request data, such as the request or a user id, out of the source.
Where the values come from
You declare vars and secrets in your Cloudflare config file. src/index.ts reads either form the same way.
{
"name": "origin-proxy",
"main": "src/index.ts",
"compatibility_date": "2026-10-01",
"vars": {
"ENVIRONMENT": "production",
"ORIGIN_URL": "https://origin.example.com",
"CACHE_TTL": "5m",
"FEATURE_FLAGS": { "beta": true }
}
}wrangler.toml takes the same fields. Keep API_TOKEN out of the file. Store the production secret with this command:
wrangler secret put API_TOKENIn local development, wrangler dev and cf dev read secrets from .dev.vars:
API_TOKEN=local-dev-token
ENVIRONMENT=developmentUnder wrangler dev, a key in .dev.vars replaces a vars entry of the same name, which is why ENVIRONMENT appears here too. ENVIRONMENT is the first of the detection keys. If none of those keys is set, Envapter.environment is 'development', so set ENVIRONMENT in your config file for production. Cloudflare's environment variables docs explain per-environment vars and wrangler secret bulk.
JSON vars and resource bindings
A JSON vars entry such as FEATURE_FLAGS in the config above arrives on env as an object. PortableSource stringifies it, and Converters.Json parses that string back.
import { } from 'cloudflare:workers';
import { , , } from 'envapt';
.(new ());
const flags = .('FEATURE_FLAGS', .); // { "beta": true } in varsflags is JsonValue | undefined, the type Converters.Json returns for any JSON. To check the shape, pass a schema to getAs or getRequired in place of Converters.Json.
A resource binding such as a KV namespace is a property of env as well. PortableSource runs it through JSON.stringify, so for a KV namespace Envapter.get('CACHE') returns '{}'. Read vars and secrets through envapt, and use bindings from env directly.
Reading fresh values on every request
Cloudflare may keep a Worker instance running after a deploy that changes only bindings (Cloudflare's note on bindings). The config from the module above keeps its old values until Cloudflare replaces the instance. If you change vars or secrets without changing code and need the new values right away, bind the handler's env argument on every request. Each bind rebuilds envapt's cache, so every request pays for a rebuild.
import { ;
export de
: Env):
.(new());
c (UR);
return w UR(.)., ), );
}
} satisfies <Env>;PortableSource accepts the typed env argument because its constructor takes any object.
Decorators
Wrangler's bundler compiles the default @Envapt accessor decorators, so they run on Workers. A decorated field reads its variable on first access. If any code reads the field before your bind runs, that read throws NoSourceBound. Compatibility lists the legacy form.