---
title: "Worker secrets and vars"
description: "Own Worker secrets and plain vars under Workers Builds. Secrets survive deploy. Put every non-secret plain var in config. keep_vars is transitional only."
url: "https://architecture.mnppi.org/operations/worker-secrets-and-vars/"
status: "Current"
last_updated: "2026-10-14"
---

# Worker secrets and vars

**Standard details**

- Status: Current
- Reviewed: October 14, 2026
- Cadence: Review after a Cloudflare deploy or secret-handling change

This page is the fleet source for Wrangler secrets and plain vars when
Workers Builds owns production deploy.

> **Note — Names only**
>
> This page lists secret names and ownership rules.
> It does not publish live values, dashboard inventories, or operator tokens.

## Secrets vs plain vars

A secret is an encrypted Worker binding.
A plain var is an unencrypted environment value.
Do not put secret-looking keys in `vars`.

Build-time Packages auth (`NODE_AUTH_TOKEN`, `NPM_CONFIG_USERCONFIG`) lives
on the Worker trigger under **Settings → Build → Build variables and
secrets**. Those values are not Worker runtime secrets. Do not put
`NODE_AUTH_TOKEN` in `secrets.required`. See
`docs/private-package-distribution.md`.

### Secrets

- Survive every deploy, with or without `keep_vars`.
- Commit names only, in `secrets.required`. Never values.
- Set production values in the Cloudflare dashboard or a documented
  break-glass operator path.

### Plain vars

- Steady state: every non-secret plain var lives in committed config
  `vars` or env-specific blocks. Config is the source of truth.
- Default deploy deletes remote vars, then writes config `vars`.
- Dashboard-only plain vars are a migration smell, not the end state.
- `keep_vars` is short-lived. Use it only while you copy dashboard-only
  plain vars into config. Then remove `keep_vars`.
- Tests and local `.dev.vars` can supply examples.

## `secrets.required`

Every Worker that uses secrets must declare those names in the current
Wrangler schema. Commit names only.

```jsonc
{
  "secrets": {
    "required": ["EXAMPLE_API_TOKEN"],
  },
}
```

`wrangler deploy` and `wrangler versions upload` fail closed when a
required secret is missing on the Worker.
`wrangler types` uses `secrets.required` as the name source.
It does not need a local `.dev.vars` file.

Fleet exemplar: `botmail-worker` pull request 6.

## Config-owned plain vars

Put every non-secret plain var in committed Wrangler `vars` or
env-specific blocks. On the `cf` path, put those keys in
`cloudflare.config.ts`. Config is the source of truth.

Do not leave production plain vars only in the dashboard.
Do not commit example or placeholder values for those keys.
Example values in config overwrite production on the next deploy.

## Transitional `keep_vars`

`keep_vars` and `--keep-vars` are deprecated as a fleet pattern.
Use them only on transitional Wrangler configs while you copy
dashboard-only plain vars into config. Then remove `keep_vars`.

`keep_vars` still writes every var that appears in config.
Committed `vars` still overwrite dashboard values for those keys.

`keep_vars` does **not** protect secrets.
Secrets survive every deploy. Do not set `keep_vars` because the Worker
has secrets.

`cf` has no `keep_vars`. `cf migrate` does not convert it.
Move plain vars into config instead of depending on `keep_vars`.
Do not invent a `cf deploy` keep-vars flag.

Known fleet leftovers. Move these plain vars into config, then drop
`keep_vars`:

- `wkd-proxy-worker` production `DOMAINS`
- `zuricert` testmail plain vars
- `personal-finance` processor Access dashboard vars

## Local and examples

Add `.dev.vars` and any secrets-file to `.gitignore`.
Never commit those files.

Commit `.dev.vars.example` with placeholders only:

```dotenv
EXAMPLE_API_TOKEN=
```

Do not put real values in comments, pull requests, or example files.
Do not commit `--secrets-file` input.

When `secrets.required` is set, local `wrangler dev` loads only those
names from `.dev.vars`. Missing names log a warning.

Workstation Keychain and direnv stay in
`docs/secrets-direnv-standard.md` in `MNPPI/org-standards`.
They do not replace Cloudflare Worker secrets.

## Deploy ownership

Workers Builds owns production and staging Worker deploy.
GitHub Actions validates the pull request.
Merge is not deploy.

Do not run workstation `wrangler deploy` against production.
Do not store routine Cloudflare deploy tokens in GitHub.
Do not use GitHub Actions `wrangler secret put` as the normal path
while Builds also deploys. That is dual ownership.
Reserve `wrangler secret put` for a documented break-glass step.

See [Cloudflare deployment](https://architecture.mnppi.org/delivery/cloudflare-deployment/index.md) and
`docs/workers-builds-migration-pattern.md` in `MNPPI/org-standards`.

## Anti-patterns

> **Caution — Do not**
>
> Using `keep_vars` because “we have secrets” is unnecessary. Secrets survive deploy.
> Do not treat `keep_vars` as the steady state for dashboard-only plain vars.
> Do not commit `CMS_API_KEY`-style values in `vars`.
> Do not run GitHub Actions `wrangler secret put` on every release while Workers Builds also deploys.
> Do not put example values of production plain vars into config.
> Do not recommend a fleet cutover to bare `cf deploy` from this page.
> Do not invent `cf` keep-vars flags. Discover commands with `cf cli search`.

## Checklist

- Declare `secrets.required` names for every Worker that uses secrets.
- Put every non-secret plain var in committed config `vars` or env blocks.
- Confirm `.dev.vars` and secrets-file paths are gitignored and untracked.
- Commit `.dev.vars.example` with placeholders only.
- If `keep_vars` is still on, copy the dashboard-only plain vars into
  config, then remove `keep_vars`.
- Point Workers Builds at the production deploy script.
- Update the repository `AGENTS.md` forbidden list for secret files and
  workstation production deploys.

## Pointers

- [Cloudflare deployment](https://architecture.mnppi.org/delivery/cloudflare-deployment/index.md) — Builds owns
  production. Secret values stay out of git. Plain vars live in config.
- [Workers Builds notify](https://architecture.mnppi.org/operations/workers-builds-notify/index.md) —
  `WATCHDOG_WEBHOOK_SECRET` is a Worker runtime secret for the
  tokenless GitHub push webhook.
- [cf CLI and cloudflare.config.ts](https://architecture.mnppi.org/delivery/cf-cli-and-cloudflare-config/index.md) —
  `cf` has no `keep_vars`. Move vars into config. Wrangler stays transitional.
- `docs/workers-builds-migration-pattern.md` in `MNPPI/org-standards` —
  leftover `keep_vars` is a migrate follow-up, not the end state.
- `docs/private-package-distribution.md` in `MNPPI/org-standards` —
  Workers Builds `NODE_AUTH_TOKEN` is a trigger Build secret, not a
  Worker runtime secret.
- `docs/secrets-direnv-standard.md` in `MNPPI/org-standards` —
  workstation Keychain names and commit-safe `.envrc`.
- Repo runbook, not an org-standard: `zuricert` `OPERATIONS-secret-management`.
  Product repos may keep their own `WORKER_SECRETS` runbooks.
