Every variable in .env.example, grouped as the file groups them, with whether you need it, what it does and which skill sets it.
This is the list in .env.example, in the file's own order. Locally the values go in .env.local. In production they go in cloudflare/.env.production.local (encrypted into the repo by the deploy) or the Vercel project's Environment Variables. Never commit a key.
Only three variables are needed for the app to start and let you sign in: NEXT_PUBLIC_SITE_URL, DATABASE_URL and BETTER_AUTH_SECRET. Everything else turns a feature on.
NEXT_PUBLIC_* values are compiled into the browser code at build time. Change one and you must rebuild, not just restart.
"Set by" names the skill that asks for or writes the value; "none" means you set it by hand when you want the feature.
Site info
| Variable |
Required |
What it does |
Set by |
NEXT_PUBLIC_SITE_URL |
yes |
The site's origin, used for links in mails, OAuth callbacks, the sitemap and Better Auth's trusted origin. http://localhost:3000 locally |
.env.example default; deploy-cloudflare / deploy-vercel for production |
NEXT_PUBLIC_PRICING_PATH |
no |
Where "buy" and "upgrade" links point. Default /#pricing |
none |
NEXT_PUBLIC_LOCALE_DETECTION |
no |
true lets next-intl pick the locale from the browser; false keeps the URL as the only source |
none |
NEXT_PUBLIC_OPTIMIZED_IMAGES |
no |
false turns off Next.js image optimization. Must be false on Cloudflare Workers |
deploy-cloudflare, deploy-vercel |
NEXT_PUBLIC_LOGIN_MODE |
no |
dialog opens sign-in in a popup; page sends the user to /login |
none |
NEXT_PUBLIC_COOKIE_CONSENT_ENABLED |
no |
true shows a cookie banner and stops signup-source tracking for visitors who decline |
none |
NEXT_PUBLIC_USER_SOURCE_TRACKING_ENABLED |
no |
true records where each new user came from (UTM, referrer, affiliate code) in user_source |
none |
NEXT_PUBLIC_RATE_LIMIT_ENABLED |
no |
true turns on Better Auth's per-IP limits in production (3 magic links per minute and so on) |
none |
NEXT_PUBLIC_EMAIL_NORMALIZATION_ENABLED |
no |
true normalizes provider-specific address variants (for example Gmail dots and +tags) before signup and newsletter checks |
none |
Database
| Variable |
Required |
What it does |
Set by |
DATABASE_URL |
yes |
Postgres connection string. On Cloudflare Workers the app connects through Hyperdrive instead, which the deploy script creates from this value |
setup-database |
Auth (Better Auth)
| Variable |
Required |
What it does |
Set by |
BETTER_AUTH_SECRET |
yes |
Signs sessions and cookies. Generate with openssl rand -base64 32; use a different one in production |
setup-auth |
NEXT_PUBLIC_GITHUB_CLIENT_ID |
no |
GitHub OAuth client id; the GitHub button appears only when set |
setup-auth |
GITHUB_CLIENT_SECRET |
with the GitHub id |
GitHub OAuth client secret |
setup-auth |
NEXT_PUBLIC_GOOGLE_CLIENT_ID |
no |
Google OAuth client id; the Google button and One Tap appear only when set |
setup-auth |
GOOGLE_CLIENT_SECRET |
with the Google id |
Google OAuth client secret |
setup-auth |
Cloudflare Turnstile
| Variable |
Required |
What it does |
Set by |
NEXT_PUBLIC_TURNSTILE_SITE_KEY |
no |
Turnstile widget key for the login form |
setup-auth |
TURNSTILE_SECRET_KEY |
with the site key |
Turns on the captcha check for the three routes that send sign-in mail |
setup-auth |
Email provider
| Variable |
Required |
What it does |
Set by |
DEFAULT_EMAIL_PROVIDER |
no |
resend (default) or cloudflare |
setup-email |
DEFAULT_ADMIN_EMAIL |
for any mail |
The sender of every mail and the recipient of admin alerts. Its domain must be verified with the provider |
setup-email |
DEFAULT_ADMIN_NAME |
no |
Sender name; empty means the site name from config/site.ts |
setup-email |
Resend
| Variable |
Required |
What it does |
Set by |
RESEND_API_KEY |
for email with Resend |
Resend API key. Email sign-in, welcome and payment mails need it (or the Cloudflare pair below) |
setup-email |
Cloudflare Email Sending
| Variable |
Required |
What it does |
Set by |
CLOUDFLARE_EMAIL_API_TOKEN |
with DEFAULT_EMAIL_PROVIDER=cloudflare |
API token with the permission Account → Email Sending → Send |
setup-email |
CLOUDFLARE_ACCOUNT_ID |
with the token above |
The Cloudflare account that sends |
setup-email |
Redis provider
| Variable |
Required |
What it does |
Set by |
DEFAULT_REDIS_PROVIDER |
no |
upstash (default) or ioredis for a self-hosted Redis (Node servers and Vercel only, not Workers) |
setup-ai |
Upstash
| Variable |
Required |
What it does |
Set by |
UPSTASH_REDIS_REST_URL |
for video jobs, rate limits, alert dedup, the signup-gift gate |
Upstash REST endpoint |
setup-ai |
UPSTASH_REDIS_REST_TOKEN |
with the URL |
Upstash REST token |
setup-ai |
Without Redis the app still runs: rate limits allow everything, the signup-gift gate grants every gift, and alert mails are not deduplicated. Async video generation does need it.
Self-hosted Redis
| Variable |
Required |
What it does |
Set by |
REDIS_URL |
with DEFAULT_REDIS_PROVIDER=ioredis |
redis:// or rediss:// connection string |
setup-ai |
Stripe
| Variable |
Required |
What it does |
Set by |
NEXT_PUBLIC_DEFAULT_CURRENCY |
no |
Currency used to format amounts and to total lifetime spend on the admin users page. Default usd |
none |
STRIPE_SECRET_KEY |
for Stripe |
Secret or restricted key. sk_live_ / rk_live_ selects the plans' live price ids, anything else test |
setup-stripe |
STRIPE_WEBHOOK_SECRET |
with Stripe |
Signing secret of the webhook endpoint (whsec_…); without it the webhook answers 400 |
setup-stripe (local), deploy-cloudflare / deploy-vercel (production) |
STRIPE_RADAR_EARLY_FRAUD_WARNING_TYPE |
no |
What to do on a Radar early fraud warning: refund,email, refund or email; empty does nothing |
none |
Creem
| Variable |
Required |
What it does |
Set by |
CREEM_API_BASE_URL |
no |
Override of the Creem API address. When empty the SDK picks the test or live API from the key |
none |
CREEM_API_KEY |
for Creem |
Creem API key; a creem_test_ key selects the plans' test product ids |
none |
CREEM_WEBHOOK_SECRET |
with Creem |
Verifies Creem webhooks; without it /api/creem/webhook answers 404 |
none |
PayPal
| Variable |
Required |
What it does |
Set by |
NEXT_PUBLIC_ENABLE_PAYPAL |
for PayPal |
true loads the PayPal script for the pricing page buttons |
none |
NEXT_PUBLIC_PAYPAL_CLIENT_ID |
for PayPal |
REST app client id. The server routes are on only when this and the secret are set |
none |
NEXT_PUBLIC_PAYPAL_ENVIRONMENT |
for PayPal |
sandbox or live; selects the API and the plans' test or live plan ids |
none |
PAYPAL_CLIENT_SECRET |
for PayPal |
REST app secret |
none |
PAYPAL_WEBHOOK_ID |
for PayPal |
Id of the webhook in the PayPal dashboard, used to verify webhook signatures |
none |
Cloudflare R2
| Variable |
Required |
What it does |
Set by |
R2_ACCOUNT_ID |
for uploads |
Cloudflare account that owns the bucket |
setup-storage |
R2_ACCESS_KEY_ID |
for uploads |
R2 API token access key |
setup-storage |
R2_SECRET_ACCESS_KEY |
for uploads |
R2 API token secret |
setup-storage |
R2_BUCKET_NAME |
for uploads |
Bucket name |
setup-storage |
R2_PUBLIC_URL |
for uploads |
Public address of the bucket, starting with https://; also allowed as an image source in next.config.mjs |
setup-storage |
AI
Any one provider key is enough to run the demos. See AI.
| Variable |
Required |
What it does |
Set by |
OPENAI_API_KEY |
no |
OpenAI models |
setup-ai |
DEEPSEEK_API_KEY |
no |
DeepSeek models |
setup-ai |
ANTHROPIC_API_KEY |
no |
Anthropic models |
setup-ai |
XAI_API_KEY |
no |
xAI (Grok) models |
setup-ai |
GOOGLE_GENERATIVE_AI_API_KEY |
no |
Google Gemini models |
setup-ai |
OPENROUTER_API_KEY |
no |
OpenRouter, many models behind one key |
setup-ai |
FIRECRAWL_API_KEY |
no |
Firecrawl web scraping, read by lib/scraping.ts |
none |
REPLICATE_API_TOKEN |
no |
Replicate image and video models |
setup-ai |
REPLICATE_WEBHOOK_SIGNING_SECRET |
no |
Verifies Replicate callbacks |
setup-ai |
FAL_KEY |
no |
fal.ai image and video models |
setup-ai |
FAL_VERIFY_WEBHOOKS |
no |
true verifies fal.ai callbacks |
setup-ai |
KIE_API_KEY |
no |
KIE models |
setup-ai |
KIE_WEBHOOK_HMAC_KEY |
no |
Verifies KIE callbacks |
setup-ai |
WEBHOOK_BASE_URL |
for async video |
Public base URL that Replicate, fal.ai and KIE call back (your domain, or a tunnel in local development) |
setup-ai |
CUSTOM_OPENAI_BASE_URL |
no |
Base URL of any OpenAI-compatible API (SiliconFlow, 302.ai, Together AI …) |
setup-ai |
CUSTOM_OPENAI_API_KEY |
with the base URL |
Key for that API |
setup-ai |
NEXT_PUBLIC_CUSTOM_OPENAI_MODELS |
with the base URL |
Its models, as model-id:Display Name,model-id-2:Display Name 2 |
setup-ai |
Translate
| Variable |
Required |
What it does |
Set by |
NEXT_PUBLIC_AI_MODEL_ID |
for the CMS translate button |
Model id used by /api/admin/translate |
none |
NEXT_PUBLIC_AI_PROVIDER |
for the CMS translate button |
openai, deepseek, anthropic, xai or google; that provider's key must be set |
none |
Analytics and ads
All optional; each script loads only when its value is set. One line each on Analytics.
| Variable |
What it does |
NEXT_PUBLIC_GOOGLE_ID |
Google Analytics 4 measurement id |
NEXT_PUBLIC_GOOGLE_ADSENSE_ID |
AdSense publisher id (the digits after ca-pub-) |
NEXT_PUBLIC_CLARITY_PROJECT_ID |
Microsoft Clarity project |
NEXT_PUBLIC_PLAUSIBLE_SRC, NEXT_PUBLIC_PLAUSIBLE_DOMAIN |
Plausible script and site domain |
PLAUSIBLE_API_KEY, PLAUSIBLE_URL |
Plausible stats API, read by the visitor-count helpers in actions/plausible |
NEXT_PUBLIC_UMAMI_SRC, NEXT_PUBLIC_UMAMI_WEBSITE_ID |
Umami script and website id |
NEXT_PUBLIC_RYBBIT_SRC, NEXT_PUBLIC_RYBBIT_SITE_ID |
Rybbit script and site id |
NEXT_PUBLIC_RYBBIT_SESSION_REPLAY |
true records Rybbit session replays |
NEXT_PUBLIC_RYBBIT_REPLAY_MASK_SELECTORS |
JSON list of CSS selectors masked in replays |
NEXT_PUBLIC_POSTHOG_HOST, NEXT_PUBLIC_POSTHOG_KEY |
PostHog host and project key |
None of these is set by a skill.
Customer support
| Variable |
Required |
What it does |
Set by |
NEXT_PUBLIC_CRISP_WEBSITE_ID |
no |
Loads the Crisp chat widget |
none |
Discord
| Variable |
Required |
What it does |
Set by |
NEXT_PUBLIC_DISCORD_INVITE_URL |
no |
Invite link shown in the site's social links and used as the support link in payment mails |
none |
DISCORD_WEBHOOK_URL |
no |
Channel webhook for order, pending-payment, duplicate, dispute and signup-gift notifications |
none |
Tolt (affiliates)
| Variable |
Required |
What it does |
Set by |
NEXT_PUBLIC_TOLT_ID |
no |
Loads the Tolt script; the referral id is passed to checkout and stored in payment metadata |
none |
Sentry
| Variable |
Required |
What it does |
Set by |
NEXT_PUBLIC_SENTRY_DSN |
no |
Turns on Sentry error reporting |
none |
SENTRY_AUTH_TOKEN |
no |
Uploads source maps during a CI build |
none |
SENTRY_ORG |
with the token |
Sentry organization slug |
none |
SENTRY_PROJECT |
with the token |
Sentry project slug |
none |
SENTRY_DEBUG |
no |
true also sends events from development |
none |
Logging (Pino)
| Variable |
Required |
What it does |
Set by |
LOG_LEVEL |
no |
trace, debug, info (default), warn, error or fatal |
none |
LOG_DIR |
no |
When set in production, also writes rotating log files to this folder. Leave empty on cloud hosts |
none |
Cron
| Variable |
Required |
What it does |
Set by |
CRON_SECRET |
for subscriptions or PayPal |
Bearer secret for /api/cron/credits (yearly-plan monthly grants, renewal and PayPal reconciliation). Without it a call is refused or is only a health check |
deploy-cloudflare, deploy-vercel |
Event tracking (optional)
| Variable |
Required |
What it does |
Set by |
TRAFFIC_SITE |
no |
Site name sent with each event. Tracking is off unless the next two are set too |
none |
TRAFFIC_INGEST_TOKEN |
no |
Token for your event collector |
none |
TRAFFIC_INGEST_URL |
no |
Your collector's endpoint; receives JSON POSTs |
none |
TRAFFIC_LOG |
no |
off records nothing; on also records local runs, under <TRAFFIC_SITE>-dev |
none |
Feedback alerts
| Variable |
Required |
What it does |
Set by |
ALERT_RESEND_KEY |
no |
Resend key used only to mail you when a user sends feedback. Separate from RESEND_API_KEY, which turns on email sign-in and welcome mail |
none (setup-email's check flags a half-set trio) |
ALERT_FROM |
with the key |
Sender of the feedback alert |
none |
ALERT_TO |
with the key |
Where the alert goes |
none |
Read by the code, not in .env.example
Two optional variables are read by the code but missing from .env.example:
| Variable |
What it does |
DISCORD_ORDER_WEBHOOK_URL |
A separate Discord channel for order notifications; falls back to DISCORD_WEBHOOK_URL |
NEXT_PUBLIC_BETTER_AUTH_URL |
Better Auth's base URL; falls back to NEXT_PUBLIC_SITE_URL |