Skip to content

@cfast/env

Every Cloudflare Worker has bindings — D1 databases, KV namespaces, R2 buckets, secrets, and environment variables. By default they are loosely typed, and misconfiguration surfaces as a runtime crash buried in a stack trace.

@cfast/env provides a single schema definition that produces TypeScript types and runtime validation in one place. Missing or misconfigured bindings are caught at Worker startup, before any request is processed, so you never find out about a missing secret at 3am.

Terminal window
pnpm add @cfast/env

No peer dependencies required.

Declare your bindings, initialize once in the Worker entry point, and use the typed result everywhere:

import { defineEnv } from "@cfast/env";
const env = defineEnv({
DB: { type: "d1" },
CACHE: { type: "kv" },
UPLOADS: { type: "r2" },
MAILGUN_API_KEY: { type: "secret" },
APP_URL: { type: "var", default: "http://localhost:8787" },
});
export default {
async fetch(request, rawEnv) {
env.init(rawEnv);
const { DB, MAILGUN_API_KEY, APP_URL } = env.get();
// ^-- D1Database ^-- string ^-- string
},
};

To derive a reusable type for function signatures:

export type Env = ReturnType<typeof env.get>;

The env instance has two methods: init() and get(). Calling init(rawEnv) validates every binding against the schema. If any fail, it throws an EnvError listing all failures at once so you can fix everything in a single pass. After a successful init(), subsequent calls are a no-op — the first valid environment wins.

get() returns the validated, fully typed object. It throws if init() has not been called successfully.

Each binding declaration maps to a concrete Cloudflare type at compile time and uses duck-type validation at runtime:

TypeTypeScript TypeValidation
d1D1DatabaseObject with .prepare() method
kvKVNamespaceObject with .get() and .put() methods
r2R2BucketObject with .put() and .head() methods
queueQueueObject with .send() method
durable-objectDurableObjectNamespaceObject with .get() and .idFromName() methods
serviceFetcherObject with .fetch() method
secretstringNon-empty string
varstringString (empty allowed, supports defaults)

The secret type requires a non-empty string and does not allow defaults — it is intended for values set via wrangler secret put. The var type supports defaults and custom validation callbacks for configuration set in wrangler.toml.

Variables can have different defaults per environment. The current environment is determined by a reserved ENVIRONMENT binding in rawEnv (valid values: "development", "staging", "production"):

const env = defineEnv({
APP_URL: {
type: "var",
default: {
development: "http://localhost:8787",
staging: "https://staging.myapp.com",
production: "https://myapp.com",
},
},
});

Set the environment in wrangler.toml:

[vars]
ENVIRONMENT = "production"
[env.staging.vars]
ENVIRONMENT = "staging"

Use the validate callback to constrain variable values beyond simple type checks:

const env = defineEnv({
LOG_LEVEL: {
type: "var",
default: "info",
validate: (v) => ["debug", "info", "warn", "error"].includes(v),
},
APP_URL: {
type: "var",
default: "http://localhost:8787",
validate: (v) => v.startsWith("http"),
},
});

EnvError collects all validation failures so you can address them together:

import { EnvError } from "@cfast/env";
try {
env.init(rawEnv);
} catch (e) {
if (e instanceof EnvError) {
console.error(e.message);
// @cfast/env: 2 binding error(s):
// - DB: Missing required D1 binding 'DB'. Check your wrangler.toml.
// - API_KEY: Missing required secret 'API_KEY'. Check your wrangler.toml.
for (const err of e.errors) {
console.error(err.key, err.message);
}
}
}

Other cfast packages accept the validated env object directly. This is the primary integration point — @cfast/env feeds typed bindings into @cfast/db, @cfast/auth, and @cfast/storage:

env.init(rawEnv);
const { DB } = env.get();
const db = createDb({ d1: DB, schema, permissions, user });

When using @cfast/core, the env schema is passed directly to createApp() and initialization is handled automatically via app.init(rawEnv).