Skip to main content
ShipFree uses environment variables for configuration. All variables are validated at build time using @t3-oss/env-nextjs and Zod for type safety.

Setup

  1. Copy .env.example to .env in your project root:
cp .env.example .env
  1. Fill in the required values for your environment
  2. Variables are validated in src/config/env.ts

Validation

All environment variables are validated using Zod schemas:
import { createEnv } from '@t3-oss/env-nextjs'
import { z } from 'zod'

export const env = createEnv({
  server: { /* server-only variables */ },
  client: { /* client-accessible variables */ },
  shared: { /* available on both */ },
  runtimeEnv: { /* process.env mappings */ }
})
Variables in the server object are only accessible server-side. Variables in client must be prefixed with NEXT_PUBLIC_ and are exposed to the browser.

Core Configuration

Database

DATABASE_URL
string
required
PostgreSQL connection string used by Drizzle ORM.
DATABASE_URL=postgres://postgres:postgres@localhost:5432/shipfree
PROD_DATABASE_URL
string
Production database URL used when running migrations in production.
PROD_DATABASE_URL=postgres://user:password@host:5432/database

Application URL

NEXT_PUBLIC_APP_URL
string
default:"http://localhost:3000"
Public URL of your application. Used for OAuth callbacks, email links, and API endpoints.Local development:
NEXT_PUBLIC_APP_URL=http://localhost:3000
Production:
NEXT_PUBLIC_APP_URL=https://yourdomain.com

Node Environment

NODE_ENV
enum
Runtime environment. Values: development, production, testValidated as:
z.enum(['development', 'test', 'production']).optional()
NEXT_PUBLIC_ENV
string
default:"development"
Client-side environment indicator exposed to the browser.

Authentication (Better-Auth)

BETTER_AUTH_SECRET
string
default:"dev-secret-change-in-production"
required
Secret key used to encrypt sessions and sign tokens.Generate a secure secret:
openssl rand -base64 32
Never use the default in production!
BETTER_AUTH_URL
string
default:"http://localhost:3000"
Base URL of your application. Must match NEXT_PUBLIC_APP_URL.
BILLING_ENABLED
boolean
default:"false"
Enable billing enforcement across the application.Validated as:
z.boolean().default(false)
EMAIL_VERIFICATION_ENABLED
boolean
default:"false"
Require email verification for new user accounts.

OAuth Providers

All OAuth providers are optional. Configure only the ones you need.

Google OAuth

GOOGLE_CLIENT_ID
string
Google OAuth client ID.Get credentials from: Google Cloud Console
GOOGLE_CLIENT_SECRET
string
Google OAuth client secret.

GitHub OAuth

GITHUB_CLIENT_ID
string
GitHub OAuth application ID.Get credentials from: GitHub Developer Settings
GITHUB_CLIENT_SECRET
string
GitHub OAuth application secret.

Microsoft OAuth

MICROSOFT_CLIENT_ID
string
Microsoft/Azure AD application ID.Get credentials from: Azure Portal
MICROSOFT_CLIENT_SECRET
string
Microsoft/Azure AD application secret.
MICROSOFT_TENANT_ID
string
default:"common"
Azure AD tenant ID. Use common for multi-tenant applications.

Facebook OAuth

FACEBOOK_CLIENT_ID
string
Facebook app ID.Get credentials from: Facebook Developers
FACEBOOK_CLIENT_SECRET
string
Facebook app secret.

Email Configuration

EMAIL_PROVIDER
enum
default:"log"
Email service provider to use for transactional emails.Options: resend, postmark, nodemailer, plunk, custom, logValidated as:
z.enum(['resend', 'postmark', 'nodemailer', 'plunk', 'custom', 'log']).default('log')
Set to log in development to print emails to console.

Email Defaults

DEFAULT_FROM_EMAIL
string
Default sender email address for outgoing emails.
DEFAULT_FROM_EMAIL=noreply@yourdomain.com
DEFAULT_FROM_NAME
string
Default sender name for outgoing emails.
DEFAULT_FROM_NAME=ShipFree
RESEND_API_KEY
string
Resend API key.Get from: Resend API Keys
RESEND_API_KEY=re_123456789
RESEND_DOMAIN
string
Verified domain for sending emails via Resend.
RESEND_DOMAIN=mail.yourdomain.com

Postmark

POSTMARK_API_TOKEN
string
Postmark server API token.Get from: Postmark

Plunk

PLUNK_API_KEY
string
Plunk API key.Get from: Plunk Settings

SMTP / Nodemailer

SMTP_HOST
string
SMTP server hostname.
SMTP_HOST=smtp.example.com
SMTP_PORT
number
SMTP server port.Validated as:
z.coerce.number().optional()
Common ports: 587 (TLS), 465 (SSL), 25 (unencrypted)
SMTP_USER
string
SMTP authentication username.
SMTP_PASS
string
SMTP authentication password.
SMTP_SECURE
string
Enable TLS/SSL. Set to "true" for port 465, "false" for port 587.

Payment Providers

ShipFree supports multiple payment providers. Configure only the one you’re using.
PAYMENT_PROVIDER
enum
default:"stripe"
Payment provider to use for billing and subscriptions.Options: stripe, polar, lemonsqueezyValidated as:
z.enum(['stripe', 'polar', 'lemonsqueezy']).default('stripe')

Stripe

STRIPE_SECRET_KEY
string
Stripe secret API key (starts with sk_).Get from: Stripe Dashboard
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET
string
Stripe webhook signing secret (starts with whsec_).Used to verify webhook events from Stripe.
STRIPE_PRICE_ID
string
Default Stripe price ID for subscriptions.

Stripe Price IDs (Public)

These are exposed to the client for displaying pricing.
NEXT_PUBLIC_STRIPE_PRICE_STARTER_MONTHLY
string
Stripe price ID for Starter plan (monthly billing).
NEXT_PUBLIC_STRIPE_PRICE_STARTER_YEARLY
string
Stripe price ID for Starter plan (yearly billing).
NEXT_PUBLIC_STRIPE_PRICE_PRO_MONTHLY
string
Stripe price ID for Pro plan (monthly billing).
NEXT_PUBLIC_STRIPE_PRICE_PRO_YEARLY
string
Stripe price ID for Pro plan (yearly billing).
NEXT_PUBLIC_STRIPE_PRICE_ENTERPRISE_MONTHLY
string
Stripe price ID for Enterprise plan (monthly billing).
NEXT_PUBLIC_STRIPE_PRICE_ENTERPRISE_YEARLY
string
Stripe price ID for Enterprise plan (yearly billing).

Polar

POLAR_ACCESS_TOKEN
string
Polar API access token.Get from: Polar Settings
POLAR_WEBHOOK_SECRET
string
Polar webhook secret for verifying webhook events.
POLAR_ORGANIZATION_ID
string
Your Polar organization ID.
POLAR_PRODUCT_ID
string
Polar product ID for subscriptions.
POLAR_ENVIRONMENT
enum
default:"production"
Polar environment.Options: production, sandboxValidated as:
z.enum(['production', 'sandbox']).default('production')

Polar Product IDs (Public)

NEXT_PUBLIC_POLAR_PRODUCT_STARTER_MONTHLY
string
Polar product ID for Starter plan (monthly).
NEXT_PUBLIC_POLAR_PRODUCT_PRO_MONTHLY
string
Polar product ID for Pro plan (monthly).
NEXT_PUBLIC_POLAR_PRODUCT_ENTERPRISE_MONTHLY
string
Polar product ID for Enterprise plan (monthly).

Lemon Squeezy

LEMONSQUEEZY_API_KEY
string
Lemon Squeezy API key.Get from: Lemon Squeezy Settings
LEMONSQUEEZY_STORE_ID
string
Your Lemon Squeezy store ID.
LEMONSQUEEZY_WEBHOOK_SECRET
string
Lemon Squeezy webhook secret.

Lemon Squeezy Product IDs (Public)

NEXT_PUBLIC_LEMONSQUEEZY_PRODUCT_STARTER_MONTHLY
string
Lemon Squeezy product ID for Starter plan (monthly).
NEXT_PUBLIC_LEMONSQUEEZY_PRODUCT_PRO_MONTHLY
string
Lemon Squeezy product ID for Pro plan (monthly).
NEXT_PUBLIC_LEMONSQUEEZY_PRODUCT_ENTERPRISE_MONTHLY
string
Lemon Squeezy product ID for Enterprise plan (monthly).

Cloudflare R2 Storage

Optional file storage using Cloudflare R2 (S3-compatible).
CLOUDFLARE_ACCOUNT_ID
string
Your Cloudflare account ID.Get from: Cloudflare R2 Dashboard
R2_ACCESS_KEY_ID
string
R2 access key ID.
R2_SECRET_ACCESS_KEY
string
R2 secret access key.
R2_BUCKET_URL
string
R2 bucket URL.
R2_BUCKET_URL=https://your-bucket.r2.dev
R2_STORAGE_BASE_URL
string
Public storage domain for accessing files.
R2_STORAGE_BASE_URL=https://your-storage-domain.com
R2_PUBLIC_BUCKET
string
Name of the public R2 bucket.
R2_PRIVATE_BUCKET
string
Name of the private R2 bucket.

Observability

Sentry

SENTRY_DSN
string
Sentry DSN for error tracking (server-side).
SENTRY_DSN=https://your-sentry-dsn
NEXT_PUBLIC_SENTRY_DSN
string
Sentry DSN for client-side error tracking.
SENTRY_AUTH_TOKEN
string
Sentry authentication token for uploading source maps.

Premium Template Purchase

These variables are for the premium template purchase feature. This is completely separate from your application’s payment system and can be removed if not needed. See the README for removal instructions.
PREMIUM_PURCHASE_STRIPE_SECRET_KEY
string
Stripe secret key for template purchases (separate from app payments).
PREMIUM_PURCHASE_STRIPE_SECRET_KEY=sk_live_...
PREMIUM_PURCHASE_STRIPE_PRICE_ID
string
Stripe price ID for the one-time $90 premium template purchase.
PREMIUM_PURCHASE_STRIPE_WEBHOOK_SECRET
string
Webhook secret for premium purchase webhooks.
NEXT_PUBLIC_PREMIUM_PURCHASE_STRIPE_PUBLISHABLE_KEY
string
Stripe publishable key for template purchases (client-side).
Discord invite link for premium template purchasers.
NEXT_PUBLIC_PREMIUM_PURCHASE_DISCORD_INVITE_LINK=https://discord.gg/your-invite-code

Utility Functions

ShipFree provides utility functions for working with environment variables in src/config/feature-flags.ts:
import { env, isTruthy, isFalsy } from '@/config/env'
import { 
  isProd, 
  isDev, 
  isTest,
  isBillingEnabled,
  isEmailVerificationEnabled,
  isStripeConfigured,
  isPolarConfigured,
  hasBillingProvider
} from '@/config/feature-flags'

// Check environment
if (isProd) {
  // Production-only logic
}

// Check feature flags
if (isBillingEnabled) {
  // Billing logic
}

// Check payment provider
if (isStripeConfigured) {
  // Stripe-specific logic
}

Type Safety

Access environment variables through the validated env object:
import { env } from '@/config/env'

// ✅ Type-safe and validated
const dbUrl = env.DATABASE_URL
const appUrl = env.NEXT_PUBLIC_APP_URL

// ❌ Don't use process.env directly
const dbUrl = process.env.DATABASE_URL // Not type-safe!
The env object ensures:
  • All required variables are present at build time
  • Values match expected types (string, number, boolean, enum)
  • Default values are applied
  • Client variables are properly prefixed