Skip to main content
Documentation

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.vars is a generated plaintext materialization of the store, written mode 0600. dev start, test run, deploy, and secrets pull rewrite 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 Cloudflare secret_text binding 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
bash

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 pull refreshes 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
bash

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:

FlagMeaningDefault
-a, --app <appId>Which app's storeDEEPSPACE_APP_ID from the nearest wrangler.toml
-c, --config <name>Which config within that storeprd, 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}.

CapValue
Per-value size32 KB
Secrets per config128
Total value bytes per config128 KB
Configs per app64

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
bash

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 start and test run re-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.vars across wrangler environments - no .dev.vars.<env> files.
  • A Cloudflare build may transiently materialize .dev.vars beside its generated worker; the scaffold deletes that output copy before completion. The root mode-0600 file is the only local materialization.

Troubleshooting#

SymptomCause and fix
Changed a secret; production still sees the old valueDeployed workers hold secret_text bindings and don't fetch at runtime. Redeploy.
Changed a secret; local dev still sees the old valueThe 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 userYou'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 configLegitimate - the first set creates the store. Reads are side-effect-free and never register anything.
Name rejectedMatch [A-Za-z_][A-Za-z0-9_]* and avoid the platform-injected names above.
A DeepSpace detected secrets comment in wrangler.toml disagrees with realityIt'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#