varsvars

Cloudflare Workers

One VARS_KEY secret. Ciphertexts bundled. Runtime decrypt via Web Crypto.

Setup

vars gen config.vars --platform serverless

This 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 app

getVars 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 preview

That'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.

  1. wrangler secret put VARS_KEY --env production (once per env).
  2. Add [env.production] vars = { VARS_ENV = "prod" } to wrangler.toml.
  3. vars gen config.vars --platform serverless.
  4. Update call sites to await getVars(env).
  5. Remove any per-secret wrangler secret put entries now managed by vars.