Environment variables
Every variable in the Next.js + Neon starter's .env.example and apps/admin/.env.local.example, what it does and where the value comes from.
The kit has two example env files. .env.example at the repository root is the one you copy to .env.local; the web app, the database package and the scripts read it. apps/admin/.env.local.example is the admin app's template, copied to apps/admin/.env.local; it only lists the origin variables that differ between the two apps. Both apps validate values with the same zod schema in apps/web/src/lib/env.ts (the admin app resolves @/lib/env to that file), and almost every variable has a local default there, so both apps start with the example files unchanged. apps/web/src/environment.d.ts mirrors the schema for process.env typing.
Variables prefixed with NEXT_PUBLIC_ are bundled into the browser. Everything else is server-only. Never put a secret in a NEXT_PUBLIC_ variable.
"Required" below means the app can't do its job in production without it. Locally the example values work as they are.
Database
| Variable | Required | Public | Purpose | Where to get it |
|---|---|---|---|---|
DATABASE_URL | Yes | No | Connection string used by both apps at runtime. In production, Neon's pooled endpoint with an application role. | Neon project connection details; locally postgresql://nextbase:nextbase@127.0.0.1:35577/nextbase |
DATABASE_DIRECT_URL | For migrations | No | Connection string used by pnpm db:migrate and drizzle-kit. In production, Neon's direct endpoint with a migration role. Falls back to DATABASE_URL when unset. | Neon project connection details; locally the same as DATABASE_URL |
DATABASE_POOL_SIZE | No | No | Maximum connections in the shared pg.Pool. Defaults to 5. Not in .env.example. | Your choice |
Auth and app origin
| Variable | Required | Public | Purpose | Where to get it |
|---|---|---|---|---|
BETTER_AUTH_URL | Yes | No | Better Auth base URL. Must be the public origin of the app that reads it: the web origin on web, the admin origin on admin. | Your deployment URL; locally http://localhost:3000 for web and http://localhost:3001 for admin |
BETTER_AUTH_SECRET | Yes | No | Signs sessions and tokens. At least 32 characters. Both apps must use the same value, because the impersonation handoff verifies a token issued by the admin app on the web app. | Generate a random string; never reuse the example |
BETTER_AUTH_TRUSTED_ORIGINS | No | No | Comma-separated extra origins Better Auth accepts, on top of BETTER_AUTH_URL and NEXT_PUBLIC_APP_URL. Useful for preview domains. | Your extra origins |
BETTER_AUTH_ORGANIZATION_LIMIT | No | No | How many workspaces one user can create. Defaults to 10. Not in .env.example; .env.test sets 100. | Your choice |
ENFORCE_PRODUCTION_SECRETS | Yes in production | No | Set to true so the app fails fast when BETTER_AUTH_SECRET is still the development default. | Set true in production |
NEXT_PUBLIC_APP_URL | Yes | Yes | Canonical origin of the app that reads it. Used for links in emails, invitation URLs, trusted origins and as the origin of that app's MCP server: /mcp/user on the web origin, /mcp/admin on the admin origin. | Your deployment URL for each app |
Independent app origins
Both apps need to know each other's address. These two are at the end of .env.example and in apps/admin/.env.local.example.
| Variable | Required | Public | Purpose | Where to get it |
|---|---|---|---|---|
NEXT_PUBLIC_WEB_APP_URL | Yes | Yes | The customer web origin. The admin app sends impersonation handoffs and customer login links there, and calls its /api/internal/admin-revalidate endpoint after admin mutations. | Your web deployment URL; locally http://localhost:3000 |
NEXT_PUBLIC_ADMIN_APP_URL | Yes | Yes | The admin origin. The web app links administrators to <admin origin>/app-admin from the sidebar and returns there when an impersonation session ends. | Your admin deployment URL; locally http://localhost:3001 |
Admin app (apps/admin/.env.local.example)
The admin app is a separate Next.js project, so it reads apps/admin/.env.local rather than the root file. Its template sets the origin variables to the admin address and leaves everything else to the shared defaults.
| Variable | Value on the admin app | Purpose |
|---|---|---|
BETTER_AUTH_URL | The admin origin, locally http://localhost:3001 | Better Auth base URL for the admin app. Its authentication cookies are host-only, so administrators sign in on the admin host. |
NEXT_PUBLIC_APP_URL | The admin origin | Canonical admin origin; also the origin of the Admin MCP. |
NEXT_PUBLIC_SITE_URL | The admin origin | Site URL for the admin app, as apps/admin/AGENTS.md asks. |
NEXT_PUBLIC_WEB_APP_URL | The web origin, locally http://localhost:3000 | Where impersonation handoffs and login links go. |
NEXT_PUBLIC_ADMIN_APP_URL | The admin origin | Same value as on web. |
In production, also give the admin app the same DATABASE_URL, BETTER_AUTH_SECRET, REVALIDATE_SECRET, S3_* and Stripe variables as the web app; it shares the web app's server modules and uses them for marketing CMS uploads and the Stripe catalog sync on the payment gateway page. Locally the defaults in env.ts cover the shared values.
| Variable | Required | Public | Purpose | Where to get it |
|---|---|---|---|---|
MAIL_FROM | Yes | No | Default sender, for example Nextbase <hello@yourdomain.com>. | A verified sender on your domain |
EMAIL_PROVIDER | Yes | No | smtp or resend. Selects the transport in apps/web/src/lib/email/send-email.ts. | smtp locally with Mailpit, resend in production |
SMTP_HOST | With smtp | No | SMTP host. | Locally 127.0.0.1 (Mailpit) |
SMTP_PORT | With smtp | No | SMTP port. Defaults to 1025. | Locally 1025 |
SMTP_USER | No | No | SMTP username. Auth is skipped when empty. | Your SMTP provider |
SMTP_PASSWORD | No | No | SMTP password. | Your SMTP provider |
SMTP_SECURE | No | No | true to force TLS. Otherwise TLS is used only on port 465. | Your SMTP provider |
RESEND_API_KEY | With resend | No | Resend API key. The app throws at send time if EMAIL_PROVIDER=resend and this is empty. | Resend dashboard |
See emails with Resend for the delivery setup.
Storage
The kit talks to any S3-compatible service through the AWS SDK. Locally that is MinIO from the Compose stack. apps/web/next.config.ts derives a next/image remotePatterns entry from S3_ENDPOINT, so uploaded images render from whatever endpoint you configure.
| Variable | Required | Public | Purpose | Where to get it |
|---|---|---|---|---|
S3_ENDPOINT | Yes | No | S3 API endpoint. Also used for the image remotePatterns entry. | Your provider; locally http://127.0.0.1:9000 |
S3_REGION | Yes | No | Bucket region. | Your provider; locally us-east-1 |
S3_ACCESS_KEY_ID | Yes | No | Access key. | Your provider; locally minio |
S3_SECRET_ACCESS_KEY | Yes | No | Secret key. | Your provider; locally minio123 |
S3_BUCKET_PUBLIC | Yes | No | Bucket for public objects such as avatars. | Create it; locally nextbase-public is created by minio-init |
S3_BUCKET_PRIVATE | Yes | No | Bucket for private uploads served through presigned URLs. | Create it; locally nextbase-private |
S3_FORCE_PATH_STYLE | No | No | true for path-style URLs (needed by MinIO). | true locally; usually false for AWS S3 |
Realtime and cache
Realtime in the browser is optional in production. apps/web/src/lib/realtime/endpoint.ts ignores NEXT_PUBLIC_CENTRIFUGO_URL in production unless it is a wss:// URL that doesn't point at localhost.
| Variable | Required | Public | Purpose | Where to get it |
|---|---|---|---|---|
NEXT_PUBLIC_CENTRIFUGO_URL | No | Yes | WebSocket URL the browser connects to. | Your Centrifugo deployment; locally ws://127.0.0.1:8000/connection/websocket |
CENTRIFUGO_URL | No | No | Server-side copy of the WebSocket URL. | Same as above |
VALKEY_URL | No | No | Valkey or Redis URL for Better Auth's secondary storage, rate limits and locks. When unset, the kit uses the Postgres tables auth_secondary_storage and auth_rate_windows and advisory locks instead. | Your Valkey host; locally redis://127.0.0.1:6379 |
CENTRIFUGO_API_URL | With realtime | No | Centrifugo HTTP API used by the outbox dispatcher to publish. | Your Centrifugo deployment; locally http://127.0.0.1:8000/api |
CENTRIFUGO_API_KEY | With realtime | No | API key for that HTTP API. | Centrifugo config; the local one is in apps/database/local/centrifugo.json |
CENTRIFUGO_TOKEN_HMAC_SECRET | With realtime | No | HMAC secret used to sign 15-minute connection tokens for the browser. At least 16 characters. | Centrifugo config; must match the server |
Billing (optional)
Billing uses the Better Auth Stripe plugin. Without these, billing pages render a disabled state.
| Variable | Required | Public | Purpose | Where to get it |
|---|---|---|---|---|
STRIPE_SECRET_KEY | No | No | Stripe secret key. | Stripe dashboard |
STRIPE_WEBHOOK_SECRET | No | No | Signing secret for the webhook at /api/stripe/webhooks on the web app. | Stripe webhook settings |
STRIPE_PRO_PRICE_ID | No | No | Monthly price for the pro plan. | Stripe product catalog |
STRIPE_PRO_ANNUAL_PRICE_ID | No | No | Annual price for pro. | Stripe product catalog |
STRIPE_ENTERPRISE_PRICE_ID | No | No | Monthly price for the enterprise plan. The plan is registered only when this is set. | Stripe product catalog |
STRIPE_ENTERPRISE_ANNUAL_PRICE_ID | No | No | Annual price for enterprise. | Stripe product catalog |
AI (optional)
| Variable | Required | Public | Purpose | Where to get it |
|---|---|---|---|---|
OPENAI_API_KEY | No | No | Enables the chat and image features. | OpenAI dashboard |
OPENAI_CHAT_MODEL | No | No | Chat model name. Defaults to gpt-4o-mini. | Your choice |
OPENAI_IMAGE_MODEL | No | No | Image model name. Defaults to dall-e-2. | Your choice |
Analytics and monitoring (optional)
| Variable | Required | Public | Purpose | Where to get it |
|---|---|---|---|---|
NEXT_PUBLIC_POSTHOG_KEY | No | Yes | PostHog project key. | PostHog project settings |
NEXT_PUBLIC_POSTHOG_HOST | No | Yes | PostHog host URL. | PostHog project settings |
NEXT_PUBLIC_GA_MEASUREMENT_ID | No | Yes | Google Analytics measurement ID. | Google Analytics |
NEXT_PUBLIC_VERCEL_ANALYTICS_ENABLED | No | Yes | true to load Vercel Analytics. | Set true on Vercel if you want it |
NEXT_PUBLIC_SENTRY_DSN | No | Yes | Sentry DSN. The Sentry wrapper in apps/web/next.config.ts is commented out; enable it there as well. | Sentry project settings |
Social login (optional)
The server registers a provider only when both of its variables are set. See authentication for what the login form shows.
| Variable | Required | Public | Purpose | Where to get it |
|---|---|---|---|---|
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET | No | No | Google OAuth app. | Google Cloud console |
GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET | No | No | GitHub OAuth app. | GitHub developer settings |
TWITTER_CLIENT_ID, TWITTER_CLIENT_SECRET | No | No | X (Twitter) OAuth app. | X developer portal |
Variables used by scripts, tests and internal routes
These are read by seeds, tests or single routes and are not in either example file.
| Variable | Purpose |
|---|---|
REVALIDATE_SECRET | Declared in env.ts as optional. Shared secret for POST /api/revalidate on the web app (sent in the x-revalidate-secret header) and for POST /api/internal/admin-revalidate (sent as a Bearer token), which the admin app calls after mutations to refresh the public web caches. Set the same value on both apps; both routes fail closed when it is unset. |
SEED_PASSWORD | Password for the fixture accounts created by pnpm --filter web db:seed. Defaults to password123. |
SEED_ADMIN_EMAIL | Email of the admin row created by the database seed. Defaults to admin@example.com. |
NEXTBASE_SEED_MODE | Set to true by the clean-migration script so seeding skips verification emails, rate limits and secondary storage. |
RUN_INTEGRATION_TESTS | Set to true by pnpm test:integration to enable the integration suites. |
MCP_TEST_PG_URL | Loopback Postgres URL for apps/web/src/mcp/pg-store.integration.test.ts. Non-loopback hosts are rejected. |
SITE_URL, NEXT_PUBLIC_SITE_URL | Set by apps/web/.env.test and the Playwright web server to the test origin on port 3100; the admin app's template sets NEXT_PUBLIC_SITE_URL to the admin origin. |
PORT | Port for the Playwright web server. Defaults to 3100. |
ADMIN_BASE_URL | Origin the Playwright config builds and starts the admin app on. Defaults to http://localhost:3101. |
For production, see the list of what to set on each Vercel project in deployment.