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 bywrangler types. Keep it regenerated whenwrangler.jsoncbindings change. Do NOT hand-redeclare a binding thatCloudflare.Envalready 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
stringwhen the code assumes it is always set,?: stringonly when it genuinely tolerates absence. - One
Envper 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 theinfisical-secretsskill 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.
Known divergence (migrate on touch)
Section titled “Known divergence (migrate on touch)”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.