Skip to content

Secrets & environments

V1 runs three environments. Every secret is bound on the smallest set of surfaces it needs — server-only secrets (service role, signing keys, peppers) live in exactly one surface and never reach the browser. Public values (SUPABASE_URL, anon key, Turnstile site key) are intentionally available in both the build env and at runtime.

EnvironmentAstro appSupabase projectDomains
devpnpm dev on localhostLocal CLI (supabase start) or a personal Supabase projectlocalhost:4321, localhost:4322
previewCloudflare Pages preview branchShared ui-plan-ai-preview Supabase projectpreview.ui.plan.ai (+ per-PR *.pages.dev)
prodCloudflare Pages main branchui-plan-ai Supabase projectui.plan.ai, api.ui.plan.ai

The Supabase Auth Site URL and Additional Redirect URLs for each project must list exactly the domains in that environment’s row. CF Pages preview URLs change per build, so add the wildcard https://*.ui-plan-ai.pages.dev to preview, never to prod.

Upstream reference: Pages env vars, Supabase redirect URLs.

The “Canonical name” column is the value’s logical identity. The “Per-surface variable” column shows the literal environment-variable name on each surface, because Astro requires a PUBLIC_ prefix to expose a value to client JS and Supabase Edge Functions reserve SUPABASE_* for the runtime-injected pair. Browser code reads PUBLIC_SUPABASE_URL and PUBLIC_SUPABASE_ANON_KEY; agents and Edge Functions read the unprefixed canonical name.

Canonical namePer-surface variableBound toNotes
SUPABASE_URLPUBLIC_SUPABASE_URL (Astro build env), SUPABASE_URL (Edge Functions, agents)CF Pages env (public), Edge Functions, agentsSame value in browser and server.
SUPABASE_ANON_KEYPUBLIC_SUPABASE_ANON_KEY (Astro build env)CF Pages env (public)Browser-only key; RLS does the protecting.
SUPABASE_SERVICE_ROLE_KEYSUPABASE_SERVICE_ROLE_KEYEdge Functions onlyNever in the Astro build env.
API_KEY_PEPPERAPI_KEY_PEPPEREdge Functions onlyServer pepper for API-key HMAC; rotatable, versioned.
CF_ACCOUNT_IDCF_ACCOUNT_IDEdge FunctionsIdentifies the Cloudflare account for Images/Stream API calls.
CF_IMAGES_TOKENCF_IMAGES_TOKENEdge FunctionsScoped API token, Images write.
CF_IMAGES_SIGNING_KEYCF_IMAGES_SIGNING_KEYEdge FunctionsMints signed delivery URLs for private images.
CF_STREAM_TOKENCF_STREAM_TOKENEdge FunctionsScoped API token, Stream write + Direct Creator Uploads.
CF_STREAM_SIGNING_KEYCF_STREAM_SIGNING_KEYEdge FunctionsMints Stream signed playback JWTs.
CF_STREAM_WEBHOOK_SECRETCF_STREAM_WEBHOOK_SECRETEdge FunctionsVerifies webhook-signature on Stream callbacks.
TURNSTILE_SITE_KEYPUBLIC_TURNSTILE_SITE_KEY (Astro build env)CF Pages env (public)Login form widget.
TURNSTILE_SECRET_KEYTURNSTILE_SECRET_KEYEdge FunctionsVerifies Turnstile tokens server-side.
APP_ORIGINSAPP_ORIGINSEdge FunctionsComma-separated allowlist used by the shared CORS module and as allowedOrigins for Cloudflare Stream direct uploads. Must list every browser origin that talks to Edge Functions (e.g. https://ui.plan.ai,https://preview.ui.plan.ai).

Upstream references: Supabase Edge Function secrets, Pages env vars.

  • Schema lives in supabase/migrations/ as timestamped SQL files.
  • Apply via supabase db push from CI; never edit schema in the Supabase Studio in preview or prod.
  • Local dev: supabase start boots a containerized stack; supabase db reset re-runs migrations + seed.
  • Seed data for first tenant (one owner, one agent plan-ai, main channel) lives in supabase/seed.sql.

Upstream: database migrations, local development.

No public/_headers file is committed today. Header hardening is an explicit deployment decision because it changes repo invariants documented in AGENTS.md and the deployment skill.

If headers are added later, start from a small baseline:

/*
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
/docs/_astro/*
Cache-Control: public, max-age=31536000, immutable
/workbench/*
Cache-Control: private, no-store

Upstream: Pages headers.

  • Supabase Postgres: PITR enabled, retention ≥ 7 days in prod.
  • Cloudflare Images + Stream: treated as durable delivery; not a backup tier.
  • Original PNG frames in Supabase Storage are the recovery source for re-deriving Cloudflare variants if needed.

Upstream: Supabase backups.