Secrets & env
Plain vars in wrangler.toml versus secrets set with wrangler secret put — which the deployed Worker needs, and how to set them.
On Cloudflare Workers, configuration reaches your app through two mechanisms. Getting the split right matters: plain vars are committed to the repo, secrets are not.
- Plain vars — non-sensitive settings declared in
apps/web/wrangler.tomlunder[vars]. They're checked into the repo and visible to anyone with access. Use them for things that aren't secret: the app name, the public origin, the database driver. - Secrets — sensitive values set with
wrangler secret put NAME. They're encrypted, never written towrangler.toml, and persist across deploys (including Workers Builds redeploys — you set them once). Use them for API keys, signing secrets, and anything you wouldn't paste into a public file.
Locally (Docker / npm run dev), both come from .env — see apps/web/.env.example, which documents every variable. On Workers, that .env split becomes [vars] + secrets.
Plain vars (wrangler.toml [vars])
These are declared in apps/web/wrangler.toml. valcraven-create rewrites the per-project ones.
| Var | Required | Purpose |
|---|---|---|
DATABASE_DRIVER | Yes ("d1") | Selects the D1 branch in lib/db.ts for Workers. |
BETTER_AUTH_URL | Yes | Better Auth base URL and a trusted origin. Must be your deployed origin. |
APP_URL | Yes | App origin used in email + Stripe links. Same value as BETTER_AUTH_URL. |
APP_NAME | Recommended | Display name in emails and MFA enrollment. |
ADMIN_EMAILS | Recommended | Comma-separated emails auto-promoted to admin on signup / DB init. |
CLOUDFLARE_ACCOUNT_ID | Optional | AI Gateway + Cloudflare Analytics API (usage/cost). |
AI_GATEWAY_ID | Optional | Routes AI through a Cloudflare AI Gateway for usage/cost analytics. |
CLOUDFLARE_ZONE_ID | Optional | Zone id of your sending domain — required by the email delivery/bounce poller (non-sensitive, so a var). |
If
BETTER_AUTH_URLis missing in[vars], the deployed app boots with an undefined base URL and auth misbehaves — a common cause of502. Always set it andAPP_URLto the real origin.
Secrets (wrangler secret put)
Set each with an interactive prompt, then confirm with wrangler secret list:
npx wrangler secret put BETTER_AUTH_SECRET # prompts for the value
npx wrangler secret list # confirm what's setNever pass a secret value as a CLI argument — it lands in your shell history. Use the interactive prompt, or pipe from stdin:
openssl rand -base64 32 | npx wrangler secret put BETTER_AUTH_SECRET.
| Secret | Required | Enables |
|---|---|---|
BETTER_AUTH_SECRET | Yes | Session signing — auth does not work without it. |
CRON_SECRET | If using scheduled jobs / campaign sends | Authorizes /api/cron and the campaign queue consumer. Without it, ticks are harmless no-ops. |
AGENTS_INTERNAL_SECRET | If using AI chat / realtime | Server-to-server auth between the web and realtime Workers. Same value on both Workers. |
AI_GATEWAY_TOKEN | If using an authenticated AI Gateway | The cf-aig-authorization token (not your Anthropic key). |
ANTHROPIC_API_KEY | If using AI chat | Drives the chat loop — set on the realtime Worker (--env production); the web Worker only needs it if its own code calls Anthropic. |
ELEVENLABS_API_KEY | If using voice | Text-to-speech; chat is text-only without it. |
STRIPE_SECRET_KEY | If using billing | Stripe API access for checkout / subscriptions. |
STRIPE_WEBHOOK_SECRET | If using billing | Verifies incoming Stripe webhook signatures. |
CF_ANALYTICS_API_TOKEN | If using the email poller / AI usage dashboard | Cloudflare GraphQL Analytics token (Zone Analytics: Read for email; Account Analytics: Read for AI usage). |
| OAuth id/secret pairs | If using social sign-in | GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET, GITHUB_*, APPLE_*, FACEBOOK_*. See OAuth setup. |
Every optional feature degrades gracefully when its key is absent — email, Stripe, chat, and voice simply stay off. Only BETTER_AUTH_SECRET (secret) and BETTER_AUTH_URL / APP_URL (vars) are needed for a working deploy.
One more: the From address
EMAIL_FROM is the From address for outgoing transactional and campaign email (e.g. Your App <noreply@yourdomain.com>). It isn't sensitive, but it must be set before you send — if it's unset the app falls back to a placeholder address and email silently fails. Set it as a [vars] entry or a secret; either works.
Two Workers, two secret stores
Secrets are per-Worker. The realtime Worker (apps/realtime) has its own secret store, and its production secrets are set with the --env production flag:
cd apps/realtime
npx wrangler secret put ANTHROPIC_API_KEY --env production
npx wrangler secret put AGENTS_INTERNAL_SECRET --env productionAGENTS_INTERNAL_SECRET must be the same value on both the web Worker and the realtime Worker — see Deploying to Cloudflare Workers → The realtime Worker for the paired-secret command.