Environment Variables
Configure site URLs, auth, database, payments, storage, email, and support chat with .env.local.
ShipNext keeps sensitive credentials and deployment-specific URLs in environment variables, while product behavior switches live in config/website.ts. Before launch:
- Copy
.env.exampleto.env.local. Do not commit.env.local. - Complete the minimum values from Quick Launch.
- Add OAuth, Stripe, R2/S3, Resend, and Crisp credentials as needed.
cp .env.example .env.localRestart pnpm dev after changing environment variables.
Files
| File | Purpose |
|---|---|
.env.example | Template with supported variables and example values |
.env.local | Local or deployment secrets; never commit it |
Variables prefixed with NEXT_PUBLIC_ are bundled for the browser and must not contain secrets. All other variables are server-only.
App and auth
For local development, URL variables must match the dev server port.
| Variable | Required | Description |
|---|---|---|
NEXT_PUBLIC_APP_URL | Yes | Public app URL with protocol. Used for Checkout, Portal, and redirects. |
BETTER_AUTH_URL | Yes | Better Auth application root URL. Keep it equal to NEXT_PUBLIC_APP_URL. |
BETTER_AUTH_SECRET | Yes | Session and token signing secret. Regenerate for production. |
NEXT_PUBLIC_APP_URL='http://localhost:3000'
BETTER_AUTH_URL='http://localhost:3000'
BETTER_AUTH_SECRET='<your-better-auth-secret>'Generate the secret with:
openssl rand -base64 32OAuth callback URLs:
- Google:
{BETTER_AUTH_URL}/api/auth/callback/google - GitHub:
{BETTER_AUTH_URL}/api/auth/callback/github
| Variable | Required | Description |
|---|---|---|
GITHUB_CLIENT_ID | Optional | GitHub OAuth App client ID |
GITHUB_CLIENT_SECRET | Optional | GitHub OAuth App client secret |
GOOGLE_CLIENT_ID | Optional | Google Cloud OAuth client ID |
GOOGLE_CLIENT_SECRET | Optional | Google Cloud OAuth client secret |
Database
| Variable | Required | Description |
|---|---|---|
DATABASE_URL | Yes | Database connection string. SQLite default: file:./app.db. |
DATABASE_URL=file:./app.dbInitialize locally:
pnpm db:pushFor PostgreSQL, use a postgresql://... URL and ensure the Drizzle adapter/provider matches the selected schema.
Payments - Stripe
Public Price IDs are read by pricing and Checkout:
| Variable | Description |
|---|---|
NEXT_PUBLIC_STARTER_MONTHLY_PRICE_ID | Starter monthly Price ID |
NEXT_PUBLIC_STARTER_YEARLY_PRICE_ID | Starter yearly Price ID |
NEXT_PUBLIC_PRO_MONTHLY_PRICE_ID | Pro monthly Price ID |
NEXT_PUBLIC_PRO_YEARLY_PRICE_ID | Pro yearly Price ID |
Server secrets:
| Variable | Required | Description |
|---|---|---|
STRIPE_SECRET_KEY | When using payments | Stripe secret key |
STRIPE_WEBHOOK_SECRET | When using webhooks | Stripe webhook signing secret |
Webhook endpoint:
https://<your-domain>/api/payment/webhookFor local testing, forward with Stripe CLI:
stripe listen --forward-to localhost:3000/api/payment/webhookObject storage - R2 / S3-compatible
| Variable | Required | Description |
|---|---|---|
S3_ENDPOINT | For R2/S3-compatible providers | API endpoint |
S3_REGION | Optional | Region, defaults to auto |
S3_ACCESS_KEY_ID | Yes | Access key ID |
S3_SECRET_ACCESS_KEY | Yes | Secret access key |
S3_BUCKET_NAME | Yes | Bucket name |
S3_PUBLIC_URL | Optional | Public object URL root |
NEXT_PUBLIC_STORAGE_PUBLIC_URL | Optional | Browser-visible public URL root |
URL resolution priority is:
S3_PUBLIC_URL/NEXT_PUBLIC_STORAGE_PUBLIC_URLS3_ENDPOINTplus bucket- AWS virtual-hosted style URL
Email - Resend
| Variable | Required | Description |
|---|---|---|
RESEND_API_KEY | When sending email | Resend API key |
MAIL_FROM_EMAIL | Reserved | Present for platform convention; current sender is websiteConfig.email.fromEmail |
Configure email.fromEmail in config/website.ts with an address from a verified Resend domain.
Support chat - Crisp
| Variable | Required | Description |
|---|---|---|
NEXT_PUBLIC_CRISP_WEBSITE_ID | Optional | Crisp Website ID. Empty means no chat widget. |
The chat component lazy-loads the SDK and syncs logged-in user email/name when available.
Full local template
NEXT_PUBLIC_APP_URL='http://localhost:3000'
BETTER_AUTH_URL='http://localhost:3000'
BETTER_AUTH_SECRET=''
DATABASE_URL=file:./app.db
GITHUB_CLIENT_ID=''
GITHUB_CLIENT_SECRET=''
GOOGLE_CLIENT_ID=''
GOOGLE_CLIENT_SECRET=''
NEXT_PUBLIC_TURNSTILE_SITE_KEY=''
TURNSTILE_SECRET_KEY=''
NEXT_PUBLIC_GOOGLE_RECAPTCHA_SITE_KEY=''
GOOGLE_RECAPTCHA_SECRET_KEY=''
NEXT_PUBLIC_HCAPTCHA_SITE_KEY=''
HCAPTCHA_SECRET_KEY=''
NEXT_PUBLIC_STARTER_MONTHLY_PRICE_ID=''
NEXT_PUBLIC_STARTER_YEARLY_PRICE_ID=''
NEXT_PUBLIC_PRO_MONTHLY_PRICE_ID=''
NEXT_PUBLIC_PRO_YEARLY_PRICE_ID=''
STRIPE_SECRET_KEY=''
STRIPE_WEBHOOK_SECRET=''
PAYMENT_TESTING_ENABLED=true
S3_ENDPOINT=''
S3_REGION='auto'
S3_ACCESS_KEY_ID=''
S3_BUCKET_NAME=''
S3_PUBLIC_URL=''
S3_SECRET_ACCESS_KEY=''
NEXT_PUBLIC_STORAGE_PUBLIC_URL=''
RESEND_API_KEY=''
MAIL_FROM_EMAIL=''
NEXT_PUBLIC_CRISP_WEBSITE_ID=''Launch checklist
-
NEXT_PUBLIC_APP_URLandBETTER_AUTH_URLuse the production HTTPS domain. -
BETTER_AUTH_SECRETis regenerated and not an example value. -
DATABASE_URLpoints to production and migrations have run. - OAuth callback URLs are updated in Google/GitHub.
- Stripe live keys and webhook secret are configured.
- R2/S3 credentials and public URL are configured.
- Resend domain is verified and
email.fromEmailis valid. - Secrets are not committed to Git.