Nextbase Docs
Nextbase Docs
HomeBlogWelcome to Nextbase v5!Choose your starterGetting startedLocal Supabase per agent
TanStack + NeonInstallationEnvironment variablesAuthenticationAgent connectionsProject structureDeployment
AuthenticationWorkspaces and the admin area
Building with agentsEmails with Resend
TroubleshootingNextbase v5
The 8 starter kitsTanStack + Neon

Environment variables

Every variable in the TanStack + Neon starter's env files, what it does and where the value comes from.

The kit ships two committed templates: .env.example at the repository root for the web app, the worker, the database scripts and Docker Compose, and apps/admin/.env.example for the admin app. Your .env files are ignored by git.

Validation lives in apps/web/src/server/env.ts (zod), which both the web and the admin server import. Every variable there has a local default that matches .env.example, so a missing value falls back to the Docker setup rather than failing. In production set each one explicitly. The database package has its own one-field schema in apps/database/src/env.ts that reads only DATABASE_DIRECT_URL.

All variables are server-only. The React Router app is server-rendered by Express, nothing uses a VITE_ prefix, and no value is handed to the browser.

Nothing loads .env into Node for you

Docker Compose reads .env for the port variables. The dev, worker, migrate and seed scripts run plain tsx and read process.env only. Export the variables you change in the shell that runs the scripts, or run them through an env loader of your choice. On Vercel, set them in the project's environment settings.

Create the file only when it is missing:

[ -f .env ] || cp .env.example .env

Application

VariableRequiredWhat it is forWhere to get the value
NODE_ENVYesdevelopment, test or production. Controls the CSP, secure cookies, Vite middleware and static servingdevelopment locally, production when deployed
PORTYesPort the Express server listens on. Default 3000Your choice. Match APP_BASE_URL. The admin dev script sets 3001
APP_BASE_URLYesCanonical origin of the app that reads it. Used by Better Auth as baseURL, as the origin check for impersonation requests, and as the MCP issuer and resource originhttp://localhost:3000 for web, http://localhost:3001 for admin, your HTTPS domains in production
TRUSTED_ORIGINSYesComma-separated browser origins allowed to make unsafe /api/v1 requests and Better Auth callsUsually the same as APP_BASE_URL. Add preview origins if you use them
BETTER_AUTH_SECRETYesSigns sessions and cookies. Must be at least 32 charactersGenerate at least 32 random bytes. Never reuse the example value
WEB_APP_URLYesThe product origin. The admin app builds the impersonation handoff link (/auth/impersonation) on ithttp://localhost:3000 locally, the web deployment's origin in production
ADMIN_APP_URLYesThe admin origin. The web app redirects to {ADMIN_APP_URL}/app-admin/users when a support session endshttp://localhost:3001 locally, the admin deployment's origin in production

Database

The kit uses four PostgreSQL credentials. Keep them separate; the whole RLS model depends on it. See authentication for what each role may do.

VariableRequiredWhat it is forWhere to get the value
DATABASE_DIRECT_URLYesMigrations, db:seed, db:check, admin:grant and drizzle-kit. Connects as migration_owner on the direct (non-pooled) endpointLocally postgresql://migration_owner:...@localhost:35432/nextbase. In Neon, the direct endpoint with the migration_owner password
DATABASE_URLYesTenant-aware application queries and outbox inserts as app_runtime through PgBouncer transaction poolingLocally port 36432. In Neon, the pooled endpoint with the app_runtime password
AUTH_DATABASE_URLYesBetter Auth tables, support handoff records and MCP credential storage as auth_runtimePooled endpoint, auth_runtime password
WORKER_DATABASE_URLYesOutbox job claims and completions as worker_runtimePooled endpoint, worker_runtime password
POSTGRES_PORTLocal onlyHost port Compose publishes for PostgreSQL. Default 35432Change when the port is taken, then update DATABASE_DIRECT_URL
PGBOUNCER_PORTLocal onlyHost port Compose publishes for PgBouncer. Default 36432Change when the port is taken, then update the three runtime URLs

Production connection strings must end with ?sslmode=verify-full. Both apps use the same four URLs.

Email

VariableRequiredWhat it is forWhere to get the value
SMTP_HOSTYesSMTP server used by the outbox worker (nodemailer). When it is smtp.resend.com, the kit adds a Resend-Idempotency-Key header per deliverylocalhost for Mailpit. Your provider's SMTP host in production
SMTP_PORTYesSMTP port. Default 31025 for MailpitProvider docs. 587 is the usual production value
SMTP_USERProductionSMTP username. Authentication is enabled only when both user and password are setYour provider. Leave empty for Mailpit
SMTP_PASSWORDProductionSMTP password or API keyYour provider. Leave empty for Mailpit
SMTP_SECUREOptionaltrue forces SMTPS. When omitted, the kit infers it from the port (465 is secure)Only set it if your provider needs an override
MAILPIT_UI_PORTLocal onlyHost port for the Mailpit web inbox. Default 38025Change when the port is taken
EMAIL_FROMYesSender shown on every emailA verified sender on your domain, for example Nextbase <no-reply@example.com>

Delivery is SMTP only; there is no RESEND_API_KEY in this kit. To use Resend, put its SMTP host in SMTP_HOST and the API key in SMTP_PASSWORD. See emails with Resend for the provider side.

Object storage

These variables configure the local MinIO container and are listed in .env.example for the storage features you add. Nothing in apps/web or apps/admin reads them yet; media_assets exists in the schema without an upload flow.

VariableRequiredWhat it is forWhere to get the value
MINIO_API_PORTLocal onlyHost port for the MinIO API. Default 39000Change together with S3_ENDPOINT
MINIO_CONSOLE_PORTLocal onlyHost port for the MinIO console. Default 39001Change when the port is taken
S3_ENDPOINTNot read yetS3-compatible endpoint. Default http://localhost:39000Your bucket's endpoint when you wire uploads
S3_REGIONNot read yetBucket region. Default us-east-1Your provider
S3_BUCKETNot read yetBucket name. Default nextbase. Compose does not create the bucket; make it in the MinIO consoleYour production bucket
S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEYNot read yetCredentials. The defaults match the MinIO root user in docker-compose.ymlLeast-privilege credentials in production

Social login

All six are optional. A provider is registered with Better Auth only when both of its values are set. The shipped login page has no provider buttons; use authClient.signIn.social from apps/web/app/lib/auth-client.ts when you add them.

VariableRequiredWhat it is forWhere to get the value
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRETOptionalGoogle sign-inGoogle Cloud console OAuth client with your APP_BASE_URL callback
GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRETOptionalGitHub sign-inGitHub OAuth app
TWITTER_CLIENT_ID, TWITTER_CLIENT_SECRETOptionalX sign-inX developer portal

Placeholders for later features

These four are in .env.example so the file has a stable shape, but nothing reads them in this kit. /api/v1/webhooks/stripe exists and answers 501 STRIPE_NOT_CONFIGURED until you implement billing.

VariableStatus
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRETNot read. Billing tables exist, no Stripe code
SENTRY_DSNNot read. No error monitoring client is installed
POSTHOG_KEYNot read. No analytics client is installed

Admin app template

apps/admin/.env.example contains only the five values that differ from the web app:

VariableValue in the templateNotes
APP_BASE_URLhttp://localhost:3001The admin app's own origin
WEB_APP_URLhttp://localhost:3000Where impersonation handoffs are sent
ADMIN_APP_URLhttp://localhost:3001Same as APP_BASE_URL for the admin app
TRUSTED_ORIGINShttp://localhost:3001The admin origin only
PORT3001Matches the dev script

The template's comment says the rest: use the same AUTH_DATABASE_URL, DATABASE_URL, WORKER_DATABASE_URL and BETTER_AUTH_SECRET as the web app. Cookies are host-only, so the two origins never share a session even with a shared secret; docs/admin-app.md describes giving each deployment its own secret if you prefer.

Platform variables not in the example files

VariableWhereWhat it is for
CRON_SECRETVercel project for apps/webBearer token Vercel Cron sends to /internal/jobs/run. The kit requires at least 32 characters and rejects the request otherwise
VERCELSet by VercelWhen present, both servers export the Express app instead of listening, and emails are dispatched with waitUntil plus the cron
NEXTBASE_WORKER_MODESet by the scriptsdaemon makes worker.ts poll forever. The worker and worker:start scripts set it for you
MCP_TEST_PG_URLLocal shellLoopback pooled connection for apps/web/src/mcp/pg-store.integration.test.ts. The test skips without it and refuses non-loopback hosts

Installation

Run the TanStack + Neon starter locally with Docker, apply migrations and seed data, start the web and admin apps, and create your first account.

Authentication

How Better Auth, global and workspace roles, the separate admin app and PostgreSQL row-level security work in the TanStack + Neon starter.

On this page

ApplicationDatabaseEmailObject storageSocial loginPlaceholders for later featuresAdmin app templatePlatform variables not in the example files