Environment Variables

Configure environment variables in Plainform

Plainform uses environment variables to configure integrations securely. All variables are validated at runtime using @t3-oss/env-nextjs and Zod schemas.

If any required variable is missing or invalid, the application won't start. This prevents runtime errors and security issues.

Quick Setup

Use .env.example template

The repository includes a .env.example file with all required variables. Copy it to create your .env file:

Create .env file
cp .env.example .env

The .env.example file contains placeholder values for all required environment variables. Edit the .env file with your actual credentials from each service provider.

Fill in your values

Open .env and replace placeholder values with your actual credentials from each service provider (see sections below for where to find them).

Validation

The app automatically validates all variables on startup via env.ts.

Complete .env Template

You'll find this template in .env.example in the repository. Copy it to .env and fill in your actual values. For production deployments, you'll need different credentials than your development environment.

.env
# Application
SITE_URL="http://localhost:3000"
NEXT_PUBLIC_SITE_URL="http://localhost:3000"

# Clerk (Authentication)
NEXT_PUBLIC_CLERK_SIGN_IN_URL="/sign-in"
NEXT_PUBLIC_CLERK_SIGN_UP_URL="/sign-up"
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="pk_test_xxxx"
CLERK_SECRET_KEY="sk_test_xxxx"
CLERK_WEBHOOK_SECRET="whsec_xxxx"

# Stripe (Payments)
STRIPE_SECRET_KEY="sk_test_xxxx"
STRIPE_PUBLISHABLE_KEY="pk_test_xxxx"
STRIPE_WEBHOOK_SECRET="whsec_xxxx"

# Supabase (Database)
DATABASE_URL="postgresql://user:password@host:6543/postgres?pgbouncer=true"
DIRECT_URL="postgresql://user:password@host:5432/postgres"
NEXT_PUBLIC_SUPABASE_URL="https://your-project-ref.supabase.co"
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY="sb_publishable_xxxx"
SUPABASE_SECRET_KEY="sb_secret_xxxx"

# AWS S3 (Storage)
AWS_ACCESS_KEY_ID="AKIAXXXXXXXXXXXX"
AWS_SECRET_ACCESS_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
AWS_S3_ENDPOINT="https://your-bucket-name.s3.your-region.amazonaws.com"
AWS_S3_REGION="your-region"
AWS_S3_BUCKET="your-bucket-name"

# Resend (Emails)
RESEND_API_KEY="re_xxxx"

# Mailchimp (Newsletter)
MAILCHIMP_API_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xx00"
MAILCHIMP_API_SERVER="xx00"
MAILCHIMP_AUDIENCE_ID="xxxxxxxxxx"

# PostHog (Analytics)
NEXT_PUBLIC_POSTHOG_KEY="phc_xxxx"
NEXT_PUBLIC_POSTHOG_HOST="https://your-region.i.posthog.com"

# Events API (Security)
EVENT_API_SECRET="your_generated_secret_here"

Variable Reference

Application

Env VariableTypeDefault
SITE_URL
string
http://localhost:3000
NEXT_PUBLIC_SITE_URL
string
http://localhost:3000

Use http://localhost:3000 in development and your production domain in production (e.g., https://yourdomain.com).

Clerk (Authentication)

Env VariableTypeDefault
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
string
pk_test_xxxx
CLERK_SECRET_KEY
string
sk_test_xxxx
CLERK_WEBHOOK_SECRET
string
whsec_xxxx
NEXT_PUBLIC_CLERK_SIGN_IN_URL
string
/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL
string
/sign-up

Where to find:

  1. Go to Clerk Dashboard
  2. Select your application
  3. Navigate to API Keys section
  4. Copy the keys for your environment
  5. For webhook secret: Webhooks → Add Endpoint → Copy Signing Secret

Stripe (Payments)

Env VariableTypeDefault
STRIPE_SECRET_KEY
string
sk_test_xxxx
STRIPE_PUBLISHABLE_KEY
string
pk_test_xxxx
STRIPE_WEBHOOK_SECRET
string
whsec_xxxx

Where to find:

  1. Go to Stripe Dashboard
  2. Navigate to Developers → API keys
  3. Copy Secret key and Publishable key
  4. For webhook secret: Developers → Webhooks → Add endpoint → Copy signing secret

Use test keys (sk_test_, pk_test_) in development. Switch to live keys in production.

Supabase (Database)

Env VariableTypeDefault
DATABASE_URL
string
postgresql://user:password@host:6543/postgres?pgbouncer=true
DIRECT_URL
string
postgresql://user:password@host:5432/postgres
NEXT_PUBLIC_SUPABASE_URL
string
https://your-project-ref.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY
string
sb_publishable_xxxx
SUPABASE_SECRET_KEY
string
sb_secret_xxxx

Where to find:

  1. Go to Supabase Dashboard
  2. Select your project
  3. Navigate to SettingsDatabase
  4. Copy the Transaction pooler connection string for DATABASE_URL
  5. Copy the Direct connection connection string for DIRECT_URL
  6. Navigate to SettingsAPI Keys
  7. Copy the Project URL for NEXT_PUBLIC_SUPABASE_URL
  8. Copy the Publishable key for NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY
  9. Copy the Secret key for SUPABASE_SECRET_KEY

DATABASE_URL uses port 6543 with pgbouncer for connection pooling. DIRECT_URL uses port 5432 for direct connections (required for Prisma migrations).

Never expose SUPABASE_SECRET_KEY in browser code or any variable prefixed with NEXT_PUBLIC_. It bypasses normal Supabase access controls and belongs only in trusted server-side code.

AWS S3 (Storage)

Env VariableTypeDefault
AWS_ACCESS_KEY_ID
string
-
AWS_SECRET_ACCESS_KEY
string
-
AWS_S3_ENDPOINT
string
https://your-bucket-name.s3.your-region.amazonaws.com
AWS_S3_REGION
string
your-region
AWS_S3_BUCKET
string
-

Where to find:

  1. Go to AWS Console
  2. Navigate to IAM → Users → Create user
  3. Attach policy: AmazonS3FullAccess
  4. Create access key → Copy Access Key ID and Secret Access Key
  5. Navigate to S3 → Create bucket → Copy bucket name and region

Replace your-region with your actual AWS region code (e.g., us-east-1, eu-west-1, ap-southeast-1). The region must match where your S3 bucket is located.

Resend (Emails)

Env VariableTypeDefault
RESEND_API_KEY
string
re_xxxx

Where to find:

  1. Go to Resend Dashboard
  2. Navigate to API Keys → Create API key → Copy the key

Mailchimp (Newsletter)

Env VariableTypeDefault
MAILCHIMP_API_KEY
string
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xx00
MAILCHIMP_API_SERVER
string
xx00
MAILCHIMP_AUDIENCE_ID
string
-

Where to find:

  1. Go to Mailchimp Dashboard
  2. Navigate to Account → Extras → API keys
  3. Create API key → Copy the key (note the server prefix at the end, e.g., -us19)
  4. Extract the server code from your API key (the part after the dash, e.g., us19)
  5. Navigate to Audience → Settings → Audience name and defaults → Copy Audience ID

The MAILCHIMP_API_SERVER value comes from the end of your API key. For example, if your API key ends with -us19, use us19 as the server value.

PostHog (Analytics)

Env VariableTypeDefault
NEXT_PUBLIC_POSTHOG_KEY
string
phc_xxxx
NEXT_PUBLIC_POSTHOG_HOST
string
https://your-region.i.posthog.com

Where to find:

  1. Go to PostHog Dashboard
  2. Navigate to Project Settings
  3. Copy Project API Key
  4. Copy Host URL (choose based on your region)

PostHog Host URLs by region:

  • US: https://us.i.posthog.com
  • EU: https://eu.i.posthog.com (recommended for GDPR compliance)

Replace your-region with either us or eu based on your PostHog project location.

Events API (Security)

Env VariableTypeDefault
EVENT_API_SECRET
string
your_generated_secret_here

How to generate:

Use a secure random string generator like RandomKeygen to generate a strong secret key.

  1. Visit RandomKeygen.com
  2. Copy a key from "Fort Knox Passwords" or "CodeIgniter Encryption Keys" section
  3. Paste into your .env file as EVENT_API_SECRET

This secret protects your Events API endpoints. Keep it secure and never commit it to version control.

Validation Schema

All environment variables are validated in env.ts using Zod:

env.ts
// From env.ts
import { createEnv } from '@t3-oss/env-nextjs';
import { z } from 'zod';

export const env = createEnv({
  server: {
    CLERK_SECRET_KEY: z.string().min(5),
    CLERK_WEBHOOK_SECRET: z.string().min(5),
    STRIPE_SECRET_KEY: z.string().min(5),
    DATABASE_URL: z.string().min(5),
    DIRECT_URL: z.string().min(5),
    SUPABASE_SECRET_KEY: z.string().min(5),
    // ... other server variables
  },
  client: {
    NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(5),
    NEXT_PUBLIC_POSTHOG_KEY: z.string().min(5),
    NEXT_PUBLIC_SUPABASE_URL: z.string().url(),
    NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: z.string().min(5),
    // ... other client variables
  },
  experimental__runtimeEnv: {
    NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
    NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL,
    NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY:
      process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY,
    // ... other client variables
  },
});

Local vs Production

Important: Production environments require different credentials than development. Never use test/development keys in production, and never use production keys in development.

Development (.env file):

  • Use test/sandbox keys for all services
  • Use http://localhost:3000 for URLs
  • Stripe: sk_test_ and pk_test_ keys
  • Clerk: pk_test_, sk_test_, and development webhook secret
  • Database: Development database connection
  • Supabase client: Development project URL and publishable key
  • Keep .env file in project root (already in .gitignore)

Production (Vercel/hosting platform):

  • Use live/production keys for all services
  • Use your production domain for URLs (e.g., https://yourdomain.com)
  • Stripe: sk_live_ and pk_live_ keys
  • Clerk: pk_live_, sk_live_, and production webhook secret
  • Database: Production database connection
  • Supabase client: Production project URL and publishable key
  • AWS S3: Production bucket with appropriate region
  • Mailchimp: Production audience/list
  • PostHog: Production project (consider EU region for GDPR)
  • Set variables in hosting platform dashboard (e.g., Vercel → Settings → Environment Variables)
  • Never commit production credentials to version control

Security Best Practices

  1. Never commit .env files - Already in .gitignore
  2. Use different keys per environment - Test keys in dev, live keys in production
  3. Rotate keys regularly - Especially after team member changes
  4. Limit key permissions - Use least-privilege principle (e.g., read-only keys where possible)
  5. Validate on startup - The env.ts schema prevents missing/invalid variables

Never expose server-side variables to the client. Only variables prefixed with NEXT_PUBLIC_ are accessible in the browser.

Troubleshooting

Next Steps

How is this guide ?

Last updated on

On this page