Environment variables
env.default reads values from the Worker environment exported by cloudflare:workers: wrangler vars, secrets and bindings. Unlike c.env, it works outside a request, for example at module scope.
import {env} from '@ltv/cwb';
const apiUrl = env.default.string('API_URL', 'http://localhost:8787');
const pageSize = env.default.int('PAGE_SIZE', 20);
const ratio = env.default.float('SAMPLE_RATIO', 0.5);
const featureX = env.default.bool('FEATURE_X', false);
const origins = env.default.array('ALLOWED_ORIGINS', []);
const flags = env.default.json('FLAGS', {beta: false});
const launchAt = env.default.date('LAUNCH_AT', new Date('2026-10-01T00:00:00Z'));
const raw = env.default('RAW_KEY', 'fallback'); // raw value, no conversion
Readers
wrangler vars can be strings or already-parsed JSON values (booleans, numbers, arrays, objects). Every typed reader accepts both.
| Reader | Accepts | Value that can't be converted |
|---|---|---|
string(key, default?) | strings; numbers and booleans via String; objects and arrays via JSON.stringify | default |
int(key, default?) | numbers (truncated), numeric strings (parseInt) | default, never NaN |
number(key, default?) | alias of int | default |
float(key, default?) | numbers, numeric strings (parseFloat) | default, never NaN |
bool(key, default?) | booleans; true/false/1/0/yes/no/on/off, case-insensitive | default |
json(key, default?) | parsed values returned as-is; strings are JSON.parsed | throws on invalid JSON |
array(key, default?) | arrays as-is; strings split on , (a,b or ["a","b"]); a number or boolean becomes one item | objects return the default |
date(key, default?) | date strings, epoch milliseconds | default, never an Invalid Date |
A key counts as present when it is readable on the env object (own or inherited, but not Object.prototype members such as toString). The typed readers treat a null value as missing.
trueAn unrecognized boolean value returns the default. With env.default.bool('FEATURE_X', true) and FEATURE_X="disabled", the result is true. Use false, 0, no or off to turn a flag off.
Return types
The return type follows the default you pass:
const url = env.default.string('API_URL', 'http://localhost'); // string
const maybeUrl = env.default.string('API_URL'); // string | undefined
const port = env.default.int('PORT', 8787); // number
Built-in constants
These are computed once, when the module loads:
| Constant | Source | Default |
|---|---|---|
NODE_ENV | NODE_ENV | production |
isDev | true when NODE_ENV is development, dev, test or local (case-insensitive) | |
isProd | !isDev | |
LOG_LEVEL | LOG_LEVEL, validated by resolveLogLevel | debug in development, info otherwise |
SERVICE_NAME | SERVICE_NAME (the logger's service field) | @ltv/cwb |
import {isDev, isDevelopmentEnvironment, NODE_ENV} from '@ltv/cwb';
if (isDev) {
// local-only behavior
}
isDevelopmentEnvironment('staging'); // false: anything that isn't an explicit dev value is production
DEVELOPMENT_ENVIRONMENTS lists the non-production values. The check is fail-safe: typos, staging, prod and an empty value all count as production, so errors are masked unless you explicitly opt into development.
NODE_ENVThe error handler decides masking per request with isProductionContext(c), which prefers c.env.NODE_ENV and falls back to the module-level value. The logger and timing default use the module-level NODE_ENV.
Configuring vars and secrets
{
"vars": {
"NODE_ENV": "production",
"LOG_LEVEL": "info",
"SERVICE_NAME": "my-api",
"PAGE_SIZE": 50, // JSON number: int() accepts it directly
"ALLOWED_ORIGINS": ["https://app.example.com"], // JSON array: array() returns it as-is
"FEATURE_X": true
},
"env": {
"staging": {
"vars": {"NODE_ENV": "production", "LOG_LEVEL": "debug", "SERVICE_NAME": "my-api-staging"}
}
}
}
- Secrets are set with
wrangler secret put NAMEand, for local development, in.dev.vars. They are read the same way (env.default.string('STRIPE_KEY', '')). - Named wrangler environments don't inherit
vars; repeat them in each environment. NODE_ENV: "staging"would still be production. KeepNODE_ENVfor the masking decision and use your own var (for exampleAPP_ENV) to name the deployment.
Typed bindings with wrangler types
wrangler types generates worker-configuration.d.ts with an Env interface for your vars and bindings. Use it for c.env in handlers:
import {fromHono} from 'chanfana';
import {createApp} from '@ltv/cwb';
const app = createApp<{Bindings: Env}>({
openApi: (hono) => {
hono.get('/config', (c) => c.json({service: c.env.SERVICE_NAME}));
return fromHono(hono);
},
});
env.default.* is not tied to the generated Env type: it reads by key name and converts at runtime. Prefer c.env for bindings such as DB or KV inside requests, and env.default for configuration values that need conversion or are read at module scope.