Cloudflare Workers
One VARS_KEY secret. Ciphertexts bundled. Runtime decrypt via Web Crypto.
Setup
vars gen config.vars --platform serverlessThis embeds per-env ciphertexts in the generated module and emits an async getVars(env) that decrypts at runtime.
Worker code
import { getVars } from '#vars'
import { Hono } from 'hono'
const app = new Hono()
app.get('/', async (c) => {
const vars = await getVars(c.env)
return c.json({ app: vars.APP_NAME, db: vars.DATABASE_URL.unwrap() })
})
export default appgetVars is async (Web Crypto is async-only). The result is memoized per-isolate — subsequent calls in the same isolate return instantly.
Wrangler config
name = "my-worker"
main = "src/index.ts"
[env.production]
vars = { VARS_ENV = "prod" }
[env.preview]
vars = { VARS_ENV = "dev" }Then set the single runtime secret once per env:
wrangler secret put VARS_KEY --env production
# paste the output of: vars key export
wrangler secret put VARS_KEY --env previewThat's the complete deploy-time config. No per-secret wrangler secret put.
Local dev
wrangler dev (and @cloudflare/vite-plugin) assembles the Worker's env
binding from your wrangler config vars plus .dev.vars — it does not
inherit the parent process environment. Wrapping the dev server in
vars run therefore does not deliver VARS_KEY to the Worker runtime.
Until env-scoped keys ship, the only local channel is .dev.vars:
# .dev.vars (must be gitignored — wrangler's default init does this)
VARS_KEY=<output of: vars key export>
VARS_ENV=dev{
"scripts": {
"dev": "wrangler dev"
}
}.dev.vars then holds the master key, which decrypts every environment's
ciphertexts — not just dev. Confirm .dev.vars is in .gitignore and treat
the file like the key itself.
Example config.vars
env(dev, prod)
public APP_NAME = "worker-api"
API_SECRET : z.string().min(16) {
dev = "worker-dev-secret-val"
prod = "worker-prod-secret-val"
}
DATABASE_URL : z.string().url() {
dev = "postgres://localhost/worker_dev"
prod = "postgres://prod.db/worker_prod"
}Migrating from --platform cloudflare
--platform cloudflare was removed because it required pushing every secret to Cloudflare via wrangler secret put, defeating the "one key" value proposition.
wrangler secret put VARS_KEY --env production(once per env).- Add
[env.production] vars = { VARS_ENV = "prod" }towrangler.toml. vars gen config.vars --platform serverless.- Update call sites to
await getVars(env). - Remove any per-secret
wrangler secret putentries now managed byvars.