Operations

Environment variables

One contract, validated at startup, reporting every problem at once and naming where each variable comes from.

Every variable the platform reads is declared in one place, together with its type, whether it is required, and its source — the integration, the storage store, or the deploy prompt. Validation reports every problem at once rather than failing at the first:

text
Environment is not usable:
  - SUPABASE_URL is required (from the Supabase integration) but is empty
  - POSTGRES_URL is required (from the Supabase integration) but is empty
  - DEMO_USER_PASSWORD is required (from the Deploy Button prompt) but is empty

The ones you will actually meet

VariableSourcePurpose
SUPABASE_URL, SUPABASE_SECRET_KEY, SUPABASE_PUBLISHABLE_KEY, SUPABASE_JWT_SECRETSupabase integrationAuth and admin access
POSTGRES_URLSupabase integrationThe pooled connection used by request-path work
POSTGRES_URL_NON_POOLINGSupabase integrationSession-mode work, such as migrations
BLOB_READ_WRITE_TOKENBlob storeAsset bytes
DEMO_USER_PASSWORDDeploy Button promptThe administrator created by the first build
CMS_LOCAL_BLOB_DIRLocal development onlyStore asset bytes on disk instead of in Blob
CMS_CORS_ORIGINSOptionalExtra browser origins allowed to make credentialed requests

Two rules

Never read the environment directly. Everything takes a parsed record, which keeps the parser pure and testable — and is required on the client, where only statically analysable public variables are inlined at all.

Never put a secret behind a public prefix. A lint rule enforces it: a public prefix inlines the value into the browser bundle, so a server-only secret documented under one would be published by a deployer doing exactly as they were told.

The example file

.env.example is the commented copy of the contract, and a check enforces both directions — a variable added to the contract but not documented fails the lint, and so does the reverse.