Environment variables
Configure secrets and vars — .dev.vars locally, wrangler secrets in production, env bindings inside functions.
Last updated:
A Lunora app reads its configuration (API keys, signing secrets, allowlists)
from the Cloudflare Worker's env. There are two kinds of value:
| Kind | Where it lives | Visible in the dashboard | Use for |
|---|---|---|---|
| var | vars block in wrangler.jsonc | yes (plain text) | non-secret config, feature flags |
| secret | wrangler secret put (encrypted, write-only) | no | API keys, signing secrets |
Both surface the same way at runtime, as properties on env. The only
difference is how you set them and whether the value is encrypted at rest. Use
a secret for anything sensitive; use a var for non-secret config you're happy
to see in plain text.
Locally — .dev.vars
Local secrets and vars live in .dev.vars (a dotenv-style file), which
wrangler dev and the Vite dev server load automatically. .dev.vars is
gitignored — never commit it. Instead commit a .dev.vars.example listing the
keys your worker needs, with placeholder values:
# .dev.vars.example
AUTH_SECRET="replace-with-openssl-rand-hex-32"
AUTH_URL="http://localhost:5173"
STORAGE_SECRET="replace-with-openssl-rand-hex-32"Auto-scaffolding
When you run lunora dev (or start Vite) without a .dev.vars,
@lunora/config offers to generate one from the
example: secret-looking placeholders (*_SECRET, *_TOKEN, …) are filled with
fresh random values, everything else is copied verbatim. If .dev.vars exists
but is missing keys the example lists (say, you just enabled an addon), it
offers to append just those. Non-interactive runs (CI) skip the prompt.
You can also manage keys by hand:
lunora env set RESEND_API_KEY "re_..." # write a single key
lunora env list # show configured keys
lunora env doctor # check .dev.vars against its examplelunora env doctor reports missing keys, still-unset placeholders, and stray
extras, and exits non-zero when anything is actionable, so it works as a CI
gate or a pre-dev sanity check.
In production — secrets and vars
Set secrets with wrangler secret put (you'll be prompted for the value;
it's encrypted and never echoed back):
wrangler secret put AUTH_SECRET
wrangler secret put RESEND_API_KEY
wrangler secret put STORAGE_SECRETOr push everything from your local .dev.vars at once:
lunora env push --yesSet non-secret vars declaratively in wrangler.jsonc; they ship with the
Worker and are visible in the dashboard:
{
// ...
"vars": {
"AUTH_URL": "https://app.example.com",
"LOG_LEVEL": "info",
},
}This is exactly how, for example, the security layer reads its CORS allowlist
from config without a code change: set LUNORA_ALLOWED_ORIGINS as a var and
the worker picks it up at request time (see Security).
Lunora never logs secret values, and the wrangler-validator plugin warns when a binding marked secret: true in your schema is missing from your secret
list, so a forgotten wrangler secret put is a build-time warning, not a production 500.
Accessing env inside your app
Cloudflare hands the Worker its bindings as the env object. In Lunora you wire
configuration from env at the worker entry (into createWorker, the
generated DO factory thunks, and adapter setup) rather than reading
globals inside handlers:
// src/server/index.ts
import { createAuth } from "@lunora/auth";
const auth = createAuth({
secret: env.AUTH_SECRET, // a secret from `wrangler secret put`
baseURL: env.AUTH_URL, // a var from wrangler.jsonc
providers: [providers.github({ clientId: env.GH_CLIENT_ID, clientSecret: env.GH_CLIENT_SECRET })],
});The same env flows into the binding thunks the generated factories accept,
for example a Vectorize binding (env) => ({ "docs-body": env.DOCS_BODY }) or a
KV namespace createKv({ namespace: env.KV }). This keeps every secret read in
one place (the entry), so a handler never reaches for a global and your functions
stay testable against the in-memory harness.
Adapters built on this env plumbing then expose the live capability on the
function ctx (ctx.vectors, ctx.ai, ctx.storage, and so on), so your
queries/mutations/actions consume configured services without touching raw
bindings themselves.
The rule: never commit secrets
.dev.varsis gitignored. Commit.dev.vars.example(placeholders only).- Production secrets go through
wrangler secret put(encrypted), not intowrangler.jsonc'svars(plain text, committed). - Run
lunora env doctorin CI to catch a checked-in placeholder or a missing key before it ships.
See also
- Deployment — the full secrets-and-deploy flow
- Security — env-driven CORS / CSRF configuration
- Testing — why keeping
envat the entry keeps handlers testable - @lunora/config — the
.dev.varsgrammar, scaffolder, and wrangler validator