Secrets
The per-app encrypted secrets store: commands, configs, caps, propagation, and troubleshooting.
On this page
Every app has exactly one platform-owned, encrypted secrets store, keyed by the immutable DEEPSPACE_APP_ID in wrangler.toml. There is no setup or link step: run the commands from the app directory (or pass --app <appId>) and they work - for the owner and collaborators alike, even before the first deploy. The first write registers the app id to you.
The store is the source of truth for every environment:
.dev.varsis a generated plaintext materialization of the store, written mode0600.dev start,test run,deploy, andsecrets pullrewrite the file whole. Never commit it, and never add or edit app secrets in it - hand edits disappear on the next write.- Deploy never reads
.dev.vars. The store is the only deploy input: deploy binds each store secret as a Cloudflaresecret_textbinding and reconciles the worker's bindings against the store. - Worker code reads
env.API_KEY- identical in dev and after deploy.
Bootstrap: there is none#
npx deepspace secrets set API_KEY=sk_live_... # works even pre-deploy
npx deepspace dev start # regenerates the cache; the worker sees env.API_KEY
Two propagation rules the CLI reminds you of:
- A deployed app picks up store changes only at the next
deploy- bindings are set at deploy time, never fetched at runtime. - A running dev session picks them up only on restart - the cache regenerates at startup, not mid-session.
secrets pullrefreshes the file without running dev.
Commands#
npx deepspace secrets list # masked: name, version, updated; --only-names, --json
npx deepspace secrets set API_KEY=sk_... B=2 # one or more KEY=value pairs; multiline/PEM values fine
npx deepspace secrets get API_KEY --plain # byte-exact when piped (> key.pem)
npx deepspace secrets delete API_KEY OLD_KEY # idempotent - already-absent keys tolerated
npx deepspace secrets pull # refresh the .dev.vars cache without running dev
npx deepspace secrets download --format json # stdout only; dotenv (default) | json | shell
npx deepspace secrets upload .env [--replace] # dotenv or JSON, `-` for stdin; --replace deletes keys absent from the file
npx deepspace secrets configs list
npx deepspace secrets configs create qa --copy-from prd # server-side copy
npx deepspace secrets configs delete qa
set, upload, and delete change the remote store only - a running dev session keeps its old values until restarted. Each of the three says so in both surfaces: human output prints Run \deepspace deploy` to apply, and --jsoncarries the same fact as"appliesAtDeploy": true`, so a script cannot mistake a successful store write for a live change.
Targeting flags#
Every subcommand takes:
| Flag | Meaning | Default |
|---|---|---|
-a, --app <appId> | Which app's store | DEEPSPACE_APP_ID from the nearest wrangler.toml |
-c, --config <name> | Which config within that store | prd, or the --env name |
-e, --env <name> | Target the [env.<name>] block - a separate app with its own store | - |
-e and -c are different axes: -e staging addresses the staging app's store (config defaulting to staging), while -c staging addresses another config of the current app. Mixing them up is caught - -e staging without an [env.staging] app id errors and points you at -c staging.
Names and caps#
Secret names match [A-Za-z_][A-Za-z0-9_]*, conventionally UPPER_SNAKE. Colliding names are rejected client-side with a clear message before any upload: every SDK-reserved binding name (DEEPSPACE_APP_ID, APP_OWNER_JWT, AUTH_JWT_PUBLIC_KEY, ASSETS, ...) plus API_WORKER_URL and PLATFORM_WORKER_URL, and any custom or Durable Object binding name declared in wrangler.toml. Config names match [A-Za-z0-9][A-Za-z0-9_-]{0,63}.
| Cap | Value |
|---|---|
| Per-value size | 32 KB |
| Secrets per config | 128 |
| Total value bytes per config | 128 KB |
| Configs per app | 64 |
Oversized writes are refused with HTTP 413.
ALLOW_DEBUG_ROUTES=true is settable - it is a normal flag, not a reserved name - but the CLI warns loudly. In production it enables the debug surface only for an authenticated app owner or platform admin; local dev sets it automatically.
Configs and environments#
The store holds flat KEY=value configs. prd is the convention for the top-level wrangler environment: a top-level deploy ships config prd. A named [env.<name>] block is a separate app with its own id and its own store, and its deploys ship config <name> of that store.
Within one app, -c <name> reads and writes another config with no linking, and configs create <new> --copy-from <existing> copies server-side - never read values and re-set them by hand. It refuses to copy over an existing config.
Seeding a staging environment's store from production crosses two apps, so --copy-from cannot do it. Pipe instead, with no temp file:
npx deepspace secrets download | npx deepspace secrets upload - -e staging
Missing is not empty#
An absent config means the app has not initialized that deploy input: deploy regenerates .dev.vars without app values, then refuses with secrets_config_missing and an executable secrets configs create <name> action. Create the config - or set its first value, which creates it - and retry.
An explicitly created empty config is intentional: deploying it removes all user-secret bindings from the live worker. The distinction is deliberate - an uninitialized app cannot silently wipe production bindings, while delete-all stays possible without a second override flag.
Deletes propagate the same way: secrets delete + redeploy removes the binding from the live worker, because deploy reconciles the worker's secret_text bindings against the store.
Setting a secret is routine config - just do it#
deepspace secrets set KEY=value is the sanctioned way to put a credential - an API key, an OAuth client secret, a webhook signing secret, a token - into an app's store. When the app owner provides a value and asks for it to be configured, an agent should run the command directly: this is routine infrastructure setup on the owner's own app, not the kind of credential entry that warrants refusing or bouncing it back to the user.
One hygiene caveat, not a blocker: the value lands in the command line - shell history, logs, any transcript. If it was shared in plaintext, rotate it afterward: regenerate it at the provider, secrets set the new value, redeploy. A non-secret value like a public OAuth client_id needs no caveat. And never secrets get --plain a value into a place it would leak - a chat reply, a committed file.
Collaborators#
A collaborator has full secrets access on the app - read, write, and configs - with writes audited under their own id. Authorization is the app role (owner, collaborator, or platform admin) keyed by DEEPSPACE_APP_ID; there is nothing to link or grant per-secret. Collaborators cannot undeploy or transfer the app.
Cache behavior#
dev startandtest runre-pull the selected config at startup and regenerate.dev.vars(SDK-managed keys plus app values). If the refresh fails, they abort rather than run against stale values. A missing config generates a value-free file locally; deploy additionally refuses until it exists.- The whole file is SDK-owned. There is no editable zone, divider grammar, import path, backup, or legacy compatibility mode.
- Store-backed apps share one
.dev.varsacross wrangler environments - no.dev.vars.<env>files. - A Cloudflare build may transiently materialize
.dev.varsbeside its generated worker; the scaffold deletes that output copy before completion. The root mode-0600file is the only local materialization.
Troubleshooting#
| Symptom | Cause and fix |
|---|---|
| Changed a secret; production still sees the old value | Deployed workers hold secret_text bindings and don't fetch at runtime. Redeploy. |
| Changed a secret; local dev still sees the old value | The cache regenerates only at startup. Restart dev, or run secrets pull. |
Not the app owner or a collaborator (403) | Ask the owner to run app collaborators add <your-email> - or your access was revoked. |
This app id is registered to another user | You're holding someone else's id (a cloned repo). npx deepspace app init --new-id forks it into your own app with a fresh store. |
list shows nothing on a fresh app or config | Legitimate - the first set creates the store. Reads are side-effect-free and never register anything. |
| Name rejected | Match [A-Za-z_][A-Za-z0-9_]* and avoid the platform-injected names above. |
A DeepSpace detected secrets comment in wrangler.toml disagrees with reality | It's a static scaffold placeholder the CLI does not maintain. secrets list is the truth. |
Ran app undeploy - are the secrets gone? | No. Undeploy keeps the store; redeploying the same app id revives the same secrets. |
Next steps#
- Deployment - how the store becomes live
secret_textbindings, and named environments. - Collaborators - who can read and write the store.
- Command reference - the flag tables.