Protocol Guide uses type-safe environment validation with Zod to ensure all required configuration is present at startup.
-
Copy the example file:
cp .env.example .env
-
Fill in required values (see below)
-
Start the server - validation happens automatically:
pnpm dev
If any required variables are missing or invalid, you'll get helpful error messages with instructions.
- Format: Must start with
sk-ant- - Get from: https://console.anthropic.com/
- Purpose: Claude API for protocol retrieval and RAG
- Example:
sk-ant-api03-...
- Format: Must start with
AIza - Get from: https://aistudio.google.com/
- Purpose: Gemini Embedding 2 Preview (1536 dim) for semantic search
- Example:
AIza...
- Format: Valid HTTPS URL
- Get from: Supabase Dashboard > Settings > API
- Example:
https://your-project.supabase.co
- Format: JWT starting with
eyJ - Purpose: Client-side database access (public)
- Get from: Supabase Dashboard > Settings > API
- Format: JWT starting with
eyJ - Purpose: Server-side database access with admin privileges
- Security: NEVER expose this in client code or commit to git
- Get from: Supabase Dashboard > Settings > API
- Format:
postgresql://... - Purpose: Direct Postgres connection for Drizzle ORM
- Get from: Supabase Dashboard > Settings > Database
- Example:
postgresql://postgres:password@db.project.supabase.co:5432/postgres
- Format:
sk_test_...orsk_live_... - Get from: https://dashboard.stripe.com/apikeys
- Purpose: Server-side Stripe API access
- Format:
pk_test_...orpk_live_... - Purpose: Client-side Stripe API access
- Format: Must start with
whsec_ - Get from: Stripe Dashboard > Webhooks
- Purpose: Verify webhook signatures from Stripe
- Format: Must start with
price_ - Purpose: Individual Pro monthly subscription price
- Get from: Stripe Dashboard > Products
- Format: Must start with
price_ - Purpose: Individual Pro annual subscription price
These are optional unless you're offering department subscriptions:
STRIPE_DEPT_SMALL_MONTHLY_PRICE_ID- Small dept (5-20 users) monthlySTRIPE_DEPT_SMALL_ANNUAL_PRICE_ID- Small dept annualSTRIPE_DEPT_LARGE_MONTHLY_PRICE_ID- Large dept (20+ users) monthlySTRIPE_DEPT_LARGE_ANNUAL_PRICE_ID- Large dept annual
- Format: Number 0-365
- Default:
7 - Purpose: Trial period length for new subscriptions
- Format: At least 32 characters
- Generate:
openssl rand -base64 32 - Purpose: Sign session cookies
- Security: Keep this secret, rotate periodically
- Format: At least 32 characters
- Generate:
openssl rand -base64 32 - Purpose: NextAuth session encryption
- Default:
http://localhost:3000 - Production: Set to your domain (e.g.,
https://protocol-guide.com)
Protocol Guide uses Redis for distributed rate limiting. If not configured, it falls back to in-memory rate limiting (not suitable for multi-instance deployments).
REDIS_URL=https://your-redis.upstash.io
REDIS_TOKEN=your_token_hereUPSTASH_REDIS_REST_URL=https://your-redis.upstash.io
UPSTASH_REDIS_REST_TOKEN=your_token_hereGet from: https://console.upstash.com/
- Values:
development,production,test - Default:
development
- Format: Number 1-65535
- Default:
3000
- Values:
debug,info,warn,error - Default:
info
- Format: Number 1-65535
- Purpose: Expo Metro bundler port
These are only needed if migrating from legacy Manus system:
OAUTH_SERVER_URL- Legacy OAuth serverOWNER_OPEN_ID- Legacy owner IDBUILT_IN_FORGE_API_URL- DEPRECATED: Forge API URLBUILT_IN_FORGE_API_KEY- DEPRECATED: Forge API key
The environment validation system provides type-safe access to all variables:
import { env } from '@/server/_core/env';
// Type-safe and guaranteed to exist
const apiKey = env.ANTHROPIC_API_KEY;
const dbUrl = env.DATABASE_URL;
const port = env.PORT; // Already parsed to numberEnvironment validation happens automatically when the server starts. If validation fails, you'll see:
❌ Environment validation failed:
ANTHROPIC_API_KEY:
Error: ANTHROPIC_API_KEY must start with "sk-ant-"
Help: Anthropic Claude API key - Get from: https://console.anthropic.com/
STRIPE_SECRET_KEY:
Error: STRIPE_SECRET_KEY is required
Help: Stripe secret key - Get from: https://dashboard.stripe.com/apikeys
📖 See .env.example for required environment variables
NODE_ENV=development
PORT=3000
# Use test mode Stripe keys
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
# Optional: Redis for testing distributed rate limiting
REDIS_URL=https://dev-redis.upstash.ioSet environment variables in Netlify Dashboard > Site Settings > Environment Variables:
-
Build variables (available during build):
NODE_ENV=productionDATABASE_URLSUPABASE_URL
-
Function variables (available at runtime):
- All Stripe keys
- All API keys
- Secrets (JWT_SECRET, etc.)
-
Deploy contexts:
- Production branch: Live Stripe keys
- Preview deploys: Test Stripe keys
# .gitignore already includes:
.env
.env.local
.env.production- JWT secrets: Every 90 days
- API keys: When team members leave
- Database passwords: Every 6 months
- Development: Test API keys, local Supabase
- Staging: Test API keys, staging Supabase
- Production: Live API keys, production Supabase
Netlify deployment will fail if required environment variables are missing - this is intentional to prevent broken deployments.
Check that:
.envfile exists- All required variables are set
- Variable formats are correct (URLs, API key prefixes)
Your API key format is incorrect. Get a fresh key from https://console.anthropic.com/
Generate a secure secret:
openssl rand -base64 32The server will automatically find an available port. Check logs for actual port.
This is a warning, not an error. The server will use in-memory rate limiting. For production, configure Redis.
import { logEnvStatus } from '@/server/_core/env';
logEnvStatus();Output:
✅ All required environment variables are validated
📦 Environment: production
🔌 Server port: 3000
🔐 Redis: configured
💳 Stripe: live mode
🤖 AI Services: Anthropic + Google Gemini
If you have an old .env file without Zod validation:
- Backup existing
.env:cp .env .env.backup - Copy new template:
cp .env.example .env - Migrate values from
.env.backupto.env - Test startup:
pnpm dev - Fix any validation errors
| Variable | Required | Format | Default |
|---|---|---|---|
| ANTHROPIC_API_KEY | Yes | sk-ant-... |
- |
| GOOGLE_API_KEY | Yes | AIza... |
- |
| SUPABASE_URL | Yes | HTTPS URL | - |
| SUPABASE_ANON_KEY | Yes | JWT | - |
| SUPABASE_SERVICE_ROLE_KEY | Yes | JWT | - |
| DATABASE_URL | Yes | postgresql://... |
- |
| STRIPE_SECRET_KEY | Yes | sk_test_... or sk_live_... |
- |
| STRIPE_PUBLISHABLE_KEY | Yes | pk_test_... or pk_live_... |
- |
| STRIPE_WEBHOOK_SECRET | Yes | whsec_... |
- |
| STRIPE_PRO_MONTHLY_PRICE_ID | Yes | price_... |
- |
| STRIPE_PRO_ANNUAL_PRICE_ID | Yes | price_... |
- |
| JWT_SECRET | Yes | 32+ chars | - |
| NEXT_AUTH_SECRET | Yes | 32+ chars | - |
| NEXT_AUTH_URL | No | URL | http://localhost:3000 |
| NODE_ENV | No | dev/prod/test | development |
| PORT | No | 1-65535 | 3000 |
| REDIS_URL | No | HTTPS URL | - |
| REDIS_TOKEN | No | String | - |
| LOG_LEVEL | No | debug/info/warn/error | info |
See .env.example for complete list with descriptions.