Skip to main content

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.

ReaderAcceptsValue that can't be converted
string(key, default?)strings; numbers and booleans via String; objects and arrays via JSON.stringifydefault
int(key, default?)numbers (truncated), numeric strings (parseInt)default, never NaN
number(key, default?)alias of intdefault
float(key, default?)numbers, numeric strings (parseFloat)default, never NaN
bool(key, default?)booleans; true/false/1/0/yes/no/on/off, case-insensitivedefault
json(key, default?)parsed values returned as-is; strings are JSON.parsedthrows on invalid JSON
array(key, default?)arrays as-is; strings split on , (a,b or ["a","b"]); a number or boolean becomes one itemobjects return the default
date(key, default?)date strings, epoch millisecondsdefault, 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.

Defaults of true

An 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:

ConstantSourceDefault
NODE_ENVNODE_ENVproduction
isDevtrue when NODE_ENV is development, dev, test or local (case-insensitive)
isProd!isDev
LOG_LEVELLOG_LEVEL, validated by resolveLogLeveldebug in development, info otherwise
SERVICE_NAMESERVICE_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.

Per-request NODE_ENV

The 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

wrangler.jsonc
{
"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 NAME and, 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. Keep NODE_ENV for the masking decision and use your own var (for example APP_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:

src/index.ts
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.