Skip to content

Secret Env Typing

Secret / Env Access — Declare Your Own Env Type

Section titled “Secret / Env Access — Declare Your Own Env Type”

Any code that reads a secret or config var off the Workers env MUST access it through a locally-declared Env type that intersects the wrangler-generated Cloudflare.Env with the explicit vars that code reads. Never read an untyped env.FOO, never as any, never re-hand-roll the binding shape.

type Env = Cloudflare.Env & {
BETTER_AUTH_URL: string;
BETTER_AUTH_SECRET: string;
GOOGLE_CLIENT_ID: string;
// …only the vars THIS code actually reads
};
  • Bindings come from Cloudflare.Env (Hyperdrive, R2, KV, …), generated by wrangler types. Keep it regenerated when wrangler.jsonc bindings change. Do NOT hand-redeclare a binding that Cloudflare.Env already provides.
  • Add only the secrets/config vars the code reads to the intersection — not a blanket catch-all. Type each as the code relies on it: required string when the code assumes it is always set, ?: string only when it genuinely tolerates absence.
  • One Env per consuming module/app. Don’t export a single giant shared env type that drifts from what any one consumer actually needs.
  • Secrets are provisioned out-of-band (Infisical → wrangler secret / .dev.vars); the type is the code-side contract, not the source of the value. See the infisical-secrets skill for provisioning.

Typed access catches a missing/renamed secret at compile time instead of a runtime undefined. Intersecting Cloudflare.Env keeps binding types in one generated source of truth instead of duplicated hand-rolled shapes that rot.

apps/web/src/server/os.ts CFEnv is the OLD hand-rolled shape (re-declares HYPERDRIVE/R2_BUCKET by hand, all-optional). When you next touch it, migrate to Cloudflare.Env & { … }. Prerequisite: add a cf-typegen script (wrangler types) so Cloudflare.Env exists.