Valcraven Docs
Reference

Environment variables

The complete reference of environment variables Valcraven reads, whether each is required, and where it's set.

This is the full list of environment variables the template reads. The authoritative source for local dev is apps/web/.env.example — copy it to .env and fill in what you need. For production on Cloudflare Workers, non-secret config lives in apps/web/wrangler.toml under [vars], and secrets are set with wrangler secret put.

Almost everything is optional — Valcraven boots with just an auth secret and a database. Add variables as you turn features on.

How to read the "Where set" column

ValueMeaning
ConfigNon-secret. Set in wrangler.toml [vars] for prod (and .env for local dev).
SecretSensitive. Set with wrangler secret put <NAME> for prod (and .env for local dev).
BindingProvided automatically by a Cloudflare binding on Workers — no env var needed in prod.

On local dev everything is read from .env regardless of the prod placement above. Client-exposed variables (prefixed NEXT_PUBLIC_) are inlined at build time — rebuild after changing them.

Core & app

VariableRequired?Where setDescription
BETTER_AUTH_SECRETYesSecretSigning secret for Better Auth sessions. Generate with openssl rand -base64 32.
BETTER_AUTH_URLYesConfigThe app's origin. Better Auth uses it as its base URL and a trusted origin. Local dev: http://localhost:3000. In wrangler.toml [vars] for prod.
APP_URLRecommendedConfigApp base URL used in emails and Stripe redirects.
APP_NAMENo (default Valcraven)ConfigDisplay name used in emails and MFA enrollment.
ADMIN_EMAILSNoConfigComma-separated emails auto-promoted to admin on DB init and signup (so admin survives a DB reset).

Database

VariableRequired?Where setDescription
DATABASE_DRIVEROn Cloudflare: d1ConfigSelects the DB backend. Set to d1 in prod [vars].
DATABASE_PATHNode/SQLite devConfigPath to the local SQLite file (default ./data/valcraven.db). Ignored when DATABASE_URL is set.
DATABASE_URLFor PostgresSecretPostgres connection string. When set, the app uses Postgres and DATABASE_PATH is ignored.

In production, the database is Cloudflare D1, wired through the DB binding in wrangler.toml — not a connection string.

OAuth providers

All optional — configure only the providers you want. See Getting Started → OAuth setup and the /configure-sso skill.

VariableRequired?Where setDescription
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRETFor Google loginSecretGoogle OAuth credentials.
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRETFor GitHub loginSecretGitHub OAuth credentials. Needs a separate OAuth app per environment (GitHub allows one callback URL per app).
APPLE_CLIENT_ID / APPLE_CLIENT_SECRETFor Apple loginSecretServices ID + a short-lived signing JWT. Apple is prod-only in practice; the JWT expires (~6 months) and must be regenerated.
FACEBOOK_CLIENT_ID / FACEBOOK_CLIENT_SECRETFor Facebook loginSecretMeta/Facebook Login credentials.

Email

Transactional email goes through Cloudflare Email Service. On Workers it uses the EMAIL binding automatically; outside Workers it falls back to the REST API, and if nothing is configured, emails are logged to the console (dev mode).

VariableRequired?Where setDescription
EMAILBindingThe send_email binding used on Workers — no config needed in prod.
EMAIL_FROMFor real sendsSecretThe From address (e.g. Valcraven <noreply@yourdomain.com>). If unset it falls back to a placeholder and email silently fails — set it in prod.
CLOUDFLARE_ACCOUNT_IDNon-Workers email / flags / AIConfigCloudflare account id. Shared across email, flags, and AI features. In prod [vars].
CF_EMAIL_API_TOKENNon-Workers emailSecretREST API token for Email Service outside Workers (Docker, VPS).
EMAIL_TRACKING_SECRETNo (falls back to BETTER_AUTH_SECRET)SecretSigns email open/click tracking tokens. Set a dedicated value in prod.
CF_ANALYTICS_API_TOKENFor delivery poller / AI usageSecretCloudflare API token. Zone Analytics: Read for the email delivery poller; Account Analytics: Read for the AI usage dashboard (one token can hold both).
CLOUDFLARE_ZONE_IDFor delivery pollerSecretZone id of the domain you send email from.

Feature flags

Feature flags use Cloudflare Flagship via the FLAGS binding on Workers, or the OpenFeature SDK fallback elsewhere. See Feature flags.

VariableRequired?Where setDescription
FLAGSBindingThe [[flagship]] binding used on Workers. Ships commented out in wrangler.toml (Flagship is private beta) — until you uncomment it with a real app_id, flag evaluation uses the OpenFeature SDK fallback.
FLAGSHIP_APP_IDNon-Workers flagsConfigOpenFeature SDK app id for the fallback path.
CF_API_TOKENNon-Workers flagsSecretToken with the Flagship Evaluate permission.
FLAG_<NAME>NoConfigLocal dev per-flag override (e.g. FLAG_MAINTENANCE_MODE=true). Flags default off in dev.

Stripe (billing)

All optional — set them to enable billing.

VariableRequired?Where setDescription
STRIPE_SECRET_KEYFor billingSecretStripe API secret key.
STRIPE_PRICE_IDFor subscriptionsConfigRecurring subscription price id.
STRIPE_LIFETIME_PRICE_IDFor lifetime dealConfigOne-time lifetime price id.
STRIPE_WEBHOOK_SECRETFor webhook syncSecretVerifies POST /api/stripe/webhook signatures.

AI chat

Chat runs through the realtime Worker (apps/realtime) over WebSockets. The agent can run BYOK Claude or Cloudflare Workers AI open models. See AI chat.

VariableRequired?Where setDescription
ANTHROPIC_API_KEYFor BYOK ClaudeSecretAnthropic API key. Can also be set (encrypted) at runtime from /admin/ai-models.
CLOUDFLARE_API_TOKENLocal Workers-AI devSecretAn "Edit Cloudflare Workers" token (+ Workers AI: Read) so wrangler dev can proxy the AI binding. Not needed if you wrangler login.
CHAT_MODELNo (defaults to Sonnet)ConfigDefault chat model id.
CHAT_MAX_TOKENSNoConfigMax tokens per response.
AI_GATEWAY_IDNoConfigCloudflare AI Gateway slug for usage/cost analytics and caching. In prod [vars].
AI_GATEWAY_TOKENFor an authenticated gatewaySecretSent as cf-aig-authorization when the gateway requires it.
PROVIDER_KEY_KEKNo (falls back to BETTER_AUTH_SECRET)SecretKey-encryption key for admin-set provider keys. Set a dedicated value in prod.
OPENAI_API_KEY / GOOGLE_API_KEYNoSecretOptional keys for other providers.

Realtime Worker

The web app reaches the realtime Worker through the REALTIME service binding. Server-to-server calls without a user cookie use a shared secret. See Realtime.

VariableRequired?Where setDescription
REALTIMEBindingService binding to the realtime Worker.
AGENTS_INTERNAL_SECRETFor live realtime pushSecretShared secret for server-to-server calls. Must match the same value on the realtime Worker. When unset, live push is skipped (notifications still persist).
REALTIME_ORIGINNoConfigOrigin of the realtime Worker's /realtime/* routes. Defaults to APP_URL, then the wrangler-dev port.

Voice

VariableRequired?Where setDescription
ELEVENLABS_API_KEYFor TTSSecretElevenLabs API key. Unset = voice is text-only.
ELEVENLABS_VOICE_IDNoConfigDefault TTS voice id.

Waitlist & API keys

VariableRequired?Where setDescription
WAITLIST_MODENoConfigGate signups behind invite codes. See Waitlist.
NEXT_PUBLIC_WAITLIST_MODENoConfigClient mirror of the above (build-time inlined).
API_KEYS_ENABLEDNoConfigServer flag enabling per-user API-key auth and the management routes. See API keys.
NEXT_PUBLIC_API_KEYS_ENABLEDNoConfigShows the API-keys card in /settings (build-time inlined). Enable together with the server flag.

Analytics

VariableRequired?Where setDescription
ANALYTICS_PROVIDERNoConfigposthog, plausible, console, or none.
POSTHOG_API_KEY / POSTHOG_HOSTFor PostHogConfigPostHog project key and host.
PLAUSIBLE_DOMAIN / PLAUSIBLE_HOSTFor PlausibleConfigPlausible domain and host.

Marketing automation

VariableRequired?Where setDescription
LOOPS_API_KEYNoSecretActivates the Loops.so drip engine when set.

Error monitoring

VariableRequired?Where setDescription
NEXT_PUBLIC_SENTRY_DSNNoConfigSentry DSN (build-time inlined).
SENTRY_AUTH_TOKENFor source-map uploadSecretSentry token for uploading source maps.

Scheduled jobs & health

Scheduled jobs run via an in-process scheduler in Node dev and via Workers Cron Triggers in prod. See Deployment.

VariableRequired?Where setDescription
ENABLE_CRONLocal Node devConfigRuns the in-process setInterval scheduler. Off by default.
CRON_SECRETFor prod cron & internal endpointsSecretAuthorizes /api/cron and internal server-to-server endpoints. Without it those return 503 (harmless no-ops).
AUDIT_LOG_RETENTION_DAYSNo (default 90)ConfigDays before audit-log entries are pruned.
HEALTH_ALERT_EMAILNo (falls back to ADMIN_EMAILS)ConfigRecipients alerted on a health failure transition.
HEALTH_ALERT_WEBHOOK_URLNoConfigURL POSTed the probe JSON on a failure transition (Slack/PagerDuty/etc.).
HEALTH_HEARTBEAT_URLNoConfigDead-man's-switch URL pinged on each healthy snapshot.
APP_VERSIONNoConfigVersion string surfaced by /api/health. Falls back to package.json.

Mobile app

The Expo app in apps/mobile reads a single variable from apps/mobile/.env (its own file, separate from the web app). See The Expo app.

VariableRequired?Where setDescription
EXPO_PUBLIC_API_URLYes (mobile)apps/mobile/.envBase URL of the Valcraven backend the app authenticates against. Must be reachable from the device (default http://localhost:3013).

Cloudflare bindings (prod)

Several features that need an env var locally are provided by a binding in production, configured in apps/web/wrangler.toml rather than as an env var:

BindingBacks
DBCloudflare D1 (database)
STORAGECloudflare R2 (file/object storage)
EMAILCloudflare Email Service
FLAGSCloudflare Flagship (feature flags) — commented out by default (private beta)
REALTIMEService binding to the realtime Worker
CAMPAIGN_QUEUECloudflare Queue (campaign send)

See Deployment → Secrets & env for the full deploy-time setup.

On this page