Environment variables
Every variable in the Next.js + Supabase starter's env files for the web and admin apps, what it does and where the value comes from.
The kit has one env file per Next.js app: apps/web/.env.local (from apps/web/.env.local.example) and apps/admin/.env.local (from apps/admin/.env.local.example). Each app also ships a .env.test with the same placeholders; the web one is the template the Playwright global setup turns into apps/web/.env.test.local. The database and email apps have no env files of their own.
pnpm supabase:sync-env writes the local Supabase URL and keys into both .env.local files. Variables prefixed NEXT_PUBLIC_ are bundled into the browser. Everything else is server-only. Never commit .env.local; it is in .gitignore.
Key names
This kit reads NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY and SUPABASE_SECRET_KEY. The older NEXT_PUBLIC_SUPABASE_ANON_KEY and SUPABASE_SERVICE_ROLE_KEY names are not read anywhere in either app.
apps/web/.env.local
Supabase
| Variable | Required | Exposure | Purpose | Where to get it |
|---|---|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | Yes | Public | Supabase API URL used by every client, middleware, the MCP adapter and the Supabase Storage remotePatterns entry in next.config.ts | pnpm supabase:sync-env, or env.API_URL from supabase status --output-format json. Production: project settings in the Supabase dashboard |
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY | Yes | Public | Browser and user-session clients | pnpm supabase:sync-env, or env.PUBLISHABLE_KEY. Production: API keys in the dashboard |
SUPABASE_SECRET_KEY | Yes | Server | Service-role client in src/supabase-clients/admin/ and src/mcp/ | pnpm supabase:sync-env, or env.SECRET_KEY. Production: API keys in the dashboard |
SUPABASE_DATABASE_PASSWORD | Optional | Server | Present in the example with the local default. Not read by the web app's source | Local default is postgres |
SUPABASE_POSTGRES_DB_URL | Tests | Server | Direct Postgres connection for the Playwright admin helpers. The global setup overwrites it in .env.test.local from env.DB_URL | env.DB_URL from supabase status |
SUPABASE_JWT_SIGNING_KEY | Optional | Server | Signs tokens for the Unkey-backed Supabase client in src/supabase-clients/unkey/ | Only needed when you issue API keys through Unkey |
SUPABASE_PROJECT_REF | Optional, commented out | Server | Reference for linking the CLI to a hosted project. Not read by the web app's source | Supabase project settings |
Stripe
| Variable | Required | Exposure | Purpose | Where to get it |
|---|---|---|---|---|
STRIPE_SECRET_KEY | For billing | Server | Payment gateway in src/payments/stripe-payment-gateway.ts | Stripe dashboard, API keys |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | For billing | Public | Stripe.js in the browser | Stripe dashboard, API keys |
STRIPE_WEBHOOK_SECRET | For billing | Server | Verifies /api/stripe/webhooks signatures | Stripe webhook endpoint, or the secret printed by stripe listen --forward-to localhost:3000/api/stripe/webhooks |
Host
| Variable | Required | Exposure | Purpose | Where to get it |
|---|---|---|---|---|
NEXT_PUBLIC_SITE_URL | Yes | Public | Canonical web origin for redirects, auth callbacks and the User MCP issuer. Falls back to NEXT_PUBLIC_VERCEL_URL when unset | http://localhost:3000 locally; your production domain in deployment |
| Variable | Required | Exposure | Purpose | Where to get it |
|---|---|---|---|---|
ADMIN_EMAIL | Yes | Server | Sender and admin notification address for app emails | Your own address; use a verified Resend sender in production |
RESEND_API_KEY | Production | Server | Resend client for app emails when USE_LOCAL_EMAIL is not true | Resend dashboard |
USE_LOCAL_EMAIL | Local | Server | When true, app emails go to the Mailpit API instead of Resend | Set true locally, remove or set false in production |
MAILPIT_URL | Local, commented out | Server | Mailpit base URL for src/utils/send-email-mailpit.ts and the Playwright mail helper. Defaults to http://localhost:54324 when unset | env.MAILPIT_URL from supabase status --output-format json |
Supabase Auth emails (verification, magic link, recovery) are not sent by the web app. Configure SMTP in the Supabase project; see emails.
Analytics, API keys, AI and error tracking (optional)
| Variable | Required | Exposure | Purpose | Where to get it |
|---|---|---|---|---|
NEXT_PUBLIC_POSTHOG_API_KEY | Optional | Public | PostHog browser and server clients | PostHog project settings |
NEXT_PUBLIC_POSTHOG_APP_ID | Optional | Public | Listed in the example and typed in environment.d.ts; not read by the app source | PostHog |
NEXT_PUBLIC_POSTHOG_HOST | Optional | Public | PostHog ingestion host | PostHog project settings |
NEXT_PUBLIC_GA_ID | Optional | Public | Google Analytics measurement ID | Google Analytics |
UNKEY_ROOT_KEY | Optional | Server | Issue and verify API keys from /user/settings/developer | Unkey dashboard |
UNKEY_API_ID | Optional | Server | The Unkey API that keys belong to | Unkey dashboard |
OPENAI_API_KEY | Optional | Server | AI chat route (/api/chat) and DALL-E image generation | OpenAI dashboard |
NEXT_PUBLIC_SENTRY_DSN | Optional | Public | Read by sentry.client.config.ts, sentry.server.config.ts and sentry.edge.config.ts. Sentry stays off until you uncomment withSentryConfig in next.config.ts | Sentry project settings |
Supabase social providers (optional)
| Variable | Required | Exposure | Purpose | Where to get it |
|---|---|---|---|---|
TWITTER_API_KEY, TWITTER_API_SECRET | Optional | Server | Read by config.toml via env(...) for [auth.external.twitter] | Twitter developer portal |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET | Optional | Server | [auth.external.google] in config.toml | Google Cloud console |
GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET | Optional | Server | [auth.external.github] in config.toml | GitHub OAuth apps |
These are consumed by the Supabase CLI when it starts the local project, not by Next.js. For a hosted project, enter them in the Supabase Auth providers settings instead. See authentication.
Observability and revalidation (optional)
| Variable | Required | Exposure | Purpose | Where to get it |
|---|---|---|---|---|
OTEL_ENABLED | Optional | Server | Turns on @vercel/otel tracing in instrumentation.ts | true or unset |
OTEL_SERVICE_NAME | Optional | Server | Service name reported in traces | Any name; example uses nextbase-ultimate-web |
OTEL_EXPORTER_OTLP_ENDPOINT | Optional | Server | OTLP collector endpoint. Standard OpenTelemetry variable, not read directly by app code | Your collector |
OTEL_EXPORTER_OTLP_HEADERS | Optional | Server | OTLP exporter headers. Standard OpenTelemetry variable | Your collector |
LOG_LEVEL | Optional | Server | pino log level for src/utils/logger.ts and src/lib/logger.ts | info, debug, and so on |
REVALIDATE_SECRET | With the admin app | Server | Expected x-revalidate-secret header for POST /api/revalidate, and the Bearer token the admin app sends to POST /api/internal/admin-revalidate after publishing content. Must match the admin app's value | Generate a random string and set it on both apps |
apps/admin/.env.local
The admin example repeats the Supabase, Stripe, email, analytics, provider, OTEL, LOG_LEVEL and REVALIDATE_SECRET lines above (without SUPABASE_POSTGRES_DB_URL, SUPABASE_JWT_SIGNING_KEY, MAILPIT_URL, OPENAI_API_KEY and NEXT_PUBLIC_SENTRY_DSN). Supabase values must be the same project as the web app. The admin app's source reads these:
| Variable | Required | Exposure | Purpose | Where to get it |
|---|---|---|---|---|
NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY, SUPABASE_SECRET_KEY | Yes | Public, Public, Server | Same meaning as in the web app; the secret key powers the service-role queries in src/data/admin/ and the Admin MCP | pnpm supabase:sync-env locally; the dashboard in production |
NEXT_PUBLIC_SITE_URL | Yes | Public | The admin app's own origin: auth callbacks and the Admin MCP issuer. Falls back to NEXT_PUBLIC_VERCEL_URL | http://localhost:3002 locally; the admin domain in production |
NEXT_PUBLIC_WEB_APP_URL | Yes | Public | The web app's origin. Used for the "Open web app" link, support sign-in and impersonation links (which land on the web app's /auth/confirm) and the cache invalidation call. Must be a bare HTTP(S) origin | http://localhost:3000 locally; the product domain in production |
REVALIDATE_SECRET | Yes for publishing | Server | Sent as a Bearer token to the web app's /api/internal/admin-revalidate so published blog, changelog and feedback changes appear on the web app | Same value as the web app |
ADMIN_EMAIL, RESEND_API_KEY, USE_LOCAL_EMAIL | For admin emails | Server | Emails sent from admin actions through src/utils/api-routes/utils.ts. The admin copy of send-email-mailpit.ts posts to http://localhost:54324 and does not read MAILPIT_URL | As for the web app |
STRIPE_SECRET_KEY, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, STRIPE_WEBHOOK_SECRET | For the payment gateway pages | Server, Public, Server | Payment gateway controls under /app-admin/payment-gateway | Stripe dashboard |
SUPABASE_DATABASE_PASSWORD, the PostHog, GA and Unkey variables, the social provider credentials, the OTEL variables and LOG_LEVEL are present in the admin example but not read by the admin app's source.
Referenced in code but not in the example files
| Variable | Exposure | Purpose | Notes |
|---|---|---|---|
NEXT_PUBLIC_APP_NAME | Public | Fallback service name in apps/web/instrumentation.ts | Optional |
NEXT_PUBLIC_VERCEL_URL | Public | Fallback origin when NEXT_PUBLIC_SITE_URL is unset, in both apps | Set by Vercel automatically |
PORT | Server | Port for the web app's pnpm start and the Playwright baseURL | Defaults to 3000 |
WEB_BASE_URL, ADMIN_BASE_URL | Test only | Point Playwright at already running web and admin servers instead of starting them | Default to http://localhost:3000 and http://localhost:3002 |
MCP_TEST_SUPABASE_URL, MCP_TEST_SUPABASE_SERVICE_KEY, MCP_TEST_SUPABASE_ANON_KEY | Test only | Enable apps/web/src/mcp/supabase-store.integration.test.ts against a local database | Loopback hosts only |
Production checklist
Web app: NEXT_PUBLIC_SITE_URL, NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY, SUPABASE_SECRET_KEY, ADMIN_EMAIL, RESEND_API_KEY and REVALIDATE_SECRET. Add the three Stripe variables if you keep billing. Do not set USE_LOCAL_EMAIL=true in production.
Admin app: the three Supabase variables, NEXT_PUBLIC_SITE_URL (admin origin), NEXT_PUBLIC_WEB_APP_URL (web origin) and the same REVALIDATE_SECRET. Add Stripe and email variables if you use those controls. The deployment page has the full flow.