Valcraven Docs
Deployment & Operations

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.toml under [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 to wrangler.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.

VarRequiredPurpose
DATABASE_DRIVERYes ("d1")Selects the D1 branch in lib/db.ts for Workers.
BETTER_AUTH_URLYesBetter Auth base URL and a trusted origin. Must be your deployed origin.
APP_URLYesApp origin used in email + Stripe links. Same value as BETTER_AUTH_URL.
APP_NAMERecommendedDisplay name in emails and MFA enrollment.
ADMIN_EMAILSRecommendedComma-separated emails auto-promoted to admin on signup / DB init.
CLOUDFLARE_ACCOUNT_IDOptionalAI Gateway + Cloudflare Analytics API (usage/cost).
AI_GATEWAY_IDOptionalRoutes AI through a Cloudflare AI Gateway for usage/cost analytics.
CLOUDFLARE_ZONE_IDOptionalZone id of your sending domain — required by the email delivery/bounce poller (non-sensitive, so a var).

If BETTER_AUTH_URL is missing in [vars], the deployed app boots with an undefined base URL and auth misbehaves — a common cause of 502. Always set it and APP_URL to 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 set

Never 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.

SecretRequiredEnables
BETTER_AUTH_SECRETYesSession signing — auth does not work without it.
CRON_SECRETIf using scheduled jobs / campaign sendsAuthorizes /api/cron and the campaign queue consumer. Without it, ticks are harmless no-ops.
AGENTS_INTERNAL_SECRETIf using AI chat / realtimeServer-to-server auth between the web and realtime Workers. Same value on both Workers.
AI_GATEWAY_TOKENIf using an authenticated AI GatewayThe cf-aig-authorization token (not your Anthropic key).
ANTHROPIC_API_KEYIf using AI chatDrives the chat loop — set on the realtime Worker (--env production); the web Worker only needs it if its own code calls Anthropic.
ELEVENLABS_API_KEYIf using voiceText-to-speech; chat is text-only without it.
STRIPE_SECRET_KEYIf using billingStripe API access for checkout / subscriptions.
STRIPE_WEBHOOK_SECRETIf using billingVerifies incoming Stripe webhook signatures.
CF_ANALYTICS_API_TOKENIf using the email poller / AI usage dashboardCloudflare GraphQL Analytics token (Zone Analytics: Read for email; Account Analytics: Read for AI usage).
OAuth id/secret pairsIf using social sign-inGOOGLE_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 production

AGENTS_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.

On this page