Environment Variables
Learn how environment variables are managed in the project.
The starter kit uses @t3-oss/env-nextjs to manage environment variables with type safety and validation. All environment variables are defined in lib/env.ts with Zod schemas.
Environment Variable Files
Create a .env file in the root directory (you can copy from .env.example):
cp .env.example .envSecurity Note
Never commit .env to version control. It's already included in .gitignore.
Use the same variable names in every environment, but store the values in the place that owns that environment:
| Environment | Where to set values | What to commit |
|---|---|---|
| Local development | Root .env file | Only .env.example with safe placeholders |
| Vercel or another host | The project's environment variable settings | Nothing containing production values |
| CI | The CI provider's encrypted secrets or variables | Workflow references to the variable names |
After changing a local value, restart the development server. After changing a hosted value, redeploy the affected environment so Next.js can include any build-time values in the new deployment.
Server and Browser Variables
The server and client schemas in lib/env.ts are a security boundary:
- Server variables such as
DATABASE_URL,BETTER_AUTH_SECRET,STRIPE_SECRET_KEYandRESEND_API_KEYmust never use theNEXT_PUBLIC_prefix. - Client variables must start with
NEXT_PUBLIC_. Their values are included in browser-accessible JavaScript and must not contain credentials or secrets. - Adding a variable to
.envdoes not add it to the validated application configuration. Declare it in the matching schema and inruntimeEnvas shown below.
Assume every public value is visible Publishable Stripe keys, Price IDs, site URLs and Turnstile site keys can be public. Stripe secret keys, webhook secrets, database credentials, Better Auth secrets, Resend keys and Turnstile secret keys must remain server-only.
Required Variables
The following environment variables are required for the application to run:
Database
DATABASE_URL=postgresql://user:password@localhost:5432/dbnameNote
The POSTGRES_* variables (POSTGRES_USER, POSTGRES_PASSWORD,
POSTGRES_DB, POSTGRES_HOST, POSTGRES_PORT) are optional and have defaults.
Only DATABASE_URL is required.
Authentication
BETTER_AUTH_SECRET=paste-a-new-random-secret-hereGenerate your own value
The authentication secret is named BETTER_AUTH_SECRET, not
AUTH_SECRET. Replace the development value copied from
.env.example before sharing or deploying the application. Every
environment should use its own secret.
Generate a Better Auth secret
Generated locally with your browser's cryptographic random number generator. The value is never sent to Achromatic.
Add it to Paste the copied line into your local .env file and use a separately generated value in production.
Optional Variables
Build your environment template
Select only the integrations you plan to enable. This generates names and placeholders locally. It never asks for or stores credentials.
# Required
DATABASE_URL="postgresql://user:password@localhost:5432/database"
BETTER_AUTH_SECRET="replace-with-a-generated-secret"
NEXT_PUBLIC_SITE_URL="http://localhost:3000"Optional means the application can start without the integration. Once you enable a feature, configure its complete variable set rather than adding one key at a time.
| Feature | Configure together | If omitted |
|---|---|---|
| AI chat | OPENAI_API_KEY | AI requests cannot reach OpenAI |
| Google sign-in | GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET | Google is not offered as a sign-in method |
| Email delivery | EMAIL_FROM, RESEND_API_KEY | Email-sending flows fail when invoked |
| Stripe billing | STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY and the Price IDs used by your configured plans or credits | Billing actions are unavailable |
| S3 storage | S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, NEXT_PUBLIC_IMAGES_BUCKET_NAME | Uploads are unavailable |
| Turnstile | TURNSTILE_SECRET_KEY, NEXT_PUBLIC_TURNSTILE_SITE_KEY | Captcha protection is disabled |
| Sentry source maps | SENTRY_ORG, SENTRY_PROJECT, SENTRY_AUTH_TOKEN | Builds do not upload source maps |
Keep paired values in sync Configure both the server and browser value for Stripe and Turnstile. A browser-only key can render an integration that the server cannot verify, while a server-only key leaves the corresponding client flow unavailable.
AI (OpenAI)
OPENAI_API_KEY=sk-...The shipped chat route uses the direct OpenAI provider. Its SDK reads
OPENAI_API_KEY from the server environment, so this variable is intentionally
not prefixed with NEXT_PUBLIC_. Remove the key if you disable the AI feature.
Authentication (OAuth)
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secretBilling (Stripe)
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
NEXT_PUBLIC_STRIPE_PRICE_PRO_MONTHLY=price_...
NEXT_PUBLIC_STRIPE_PRICE_PRO_YEARLY=price_...
NEXT_PUBLIC_STRIPE_PRICE_LIFETIME=price_...
NEXT_PUBLIC_STRIPE_PRICE_CREDITS_STARTER=price_...
NEXT_PUBLIC_STRIPE_PRICE_CREDITS_BASIC=price_...
NEXT_PUBLIC_STRIPE_PRICE_CREDITS_PRO=price_...Email (Resend)
EMAIL_FROM=noreply@example.com
RESEND_API_KEY=re_...Storage (S3)
S3_ACCESS_KEY_ID=your-access-key
S3_SECRET_ACCESS_KEY=your-secret-key
S3_ENDPOINT=https://your-s3-compatible-endpoint.example
S3_REGION=your-provider-region
NEXT_PUBLIC_IMAGES_BUCKET_NAME=your-bucket-nameUse the endpoint and signing region supplied by your storage provider. The
storage client falls back to auto only when S3_REGION is omitted.
Monitoring (Sentry)
SENTRY_ORG=your-org
SENTRY_PROJECT=your-project
SENTRY_AUTH_TOKEN=your-auth-token
NEXT_PUBLIC_SENTRY_DSN=https://...@sentry.io/...Captcha (Cloudflare Turnstile)
TURNSTILE_SECRET_KEY=your-secret-key
NEXT_PUBLIC_TURNSTILE_SITE_KEY=your-site-keySite Configuration
NEXT_PUBLIC_SITE_URL=https://your-domain.com
NEXT_PUBLIC_LOG_LEVEL=infoType Safety
The project uses TypeScript and Zod to ensure type safety for environment variables. All variables are defined in lib/env.ts with validation schemas.
Type Safety Environment variables are validated at build time and runtime. If a required variable is missing or has an invalid type, the application will fail to start with a clear error message.
Adding New Variables
- Add the variable to
lib/env.tsin the appropriate schema (server or client) - Add the variable to
.env.example(without sensitive values) - Add the variable to your
.envfile with the actual value - Add the variable to
runtimeEnvinlib/env.ts - Restart your development server
Example: Adding a Server Variable
server: {
// ... existing variables
MY_NEW_VAR: z.string().min(1),
},runtimeEnv: {
// ... existing variables
MY_NEW_VAR: process.env.MY_NEW_VAR,
},Example: Adding a Client Variable
Client variables must be prefixed with NEXT_PUBLIC_:
client: {
// ... existing variables
NEXT_PUBLIC_MY_VAR: z.string().optional(),
},runtimeEnv: {
// ... existing variables
NEXT_PUBLIC_MY_VAR: process.env.NEXT_PUBLIC_MY_VAR,
},Production
For production deployments, set environment variables in your hosting platform's dashboard (Vercel, Railway, etc.). Never commit production secrets to your repository.
Skipping Validation
For Docker builds or CI/CD pipelines, you can skip environment variable validation:
SKIP_ENV_VALIDATION=true bun run buildThis is useful when environment variables are provided at runtime rather than build time. It only skips schema validation. It does not supply missing values, so the related feature can still fail when used.