STRIPE STARTER · ENVIRONMENT VARIABLES
Environment variables
Every env var, why it exists, what happens if it's missing. Validated with T3 Env + Zod — the app refuses to boot with a typo or a wrong-prefix key.
We don't read process.env directly anywhere except src/lib/env.ts. Every variable is declared there, validated by Zod on startup, and exported as a typed object. Typos and wrong-prefix Stripe keys are caught at boot, not at the moment a customer tries to pay.
Required (the app won't boot without these)
# Stripe — server (NEVER expose)
STRIPE_SECRET_KEY="sk_test_..." # validated: must start with sk_
STRIPE_WEBHOOK_SECRET="whsec_..." # validated: must start with whsec_
# Stripe — client (safe in browser)
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_..." # validated: must start with pk_Optional (the app works without them)
# Canonical URL — used for success/cancel redirect URLs in prod
NEXT_PUBLIC_APP_URL="https://yourdomain.com"
# Upstash Redis — rate limiting (graceful no-op if missing)
UPSTASH_REDIS_REST_URL="https://xxx.upstash.io"
UPSTASH_REDIS_REST_TOKEN="AXxx..."How the validation catches your mistakes
// src/lib/env.ts
STRIPE_SECRET_KEY: z
.string()
.startsWith("sk_", "Must be a valid Stripe secret key (sk_test_ or sk_live_)"),
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z
.string()
.startsWith("pk_", "Must be a valid Stripe publishable key (pk_test_ or pk_live_)"),
STRIPE_WEBHOOK_SECRET: z
.string()
.startsWith("whsec_", "Must be a valid Stripe webhook signing secret"),Adding a new variable
- Add it to src/lib/env.ts (server or client section + runtimeEnv mapping).
- Add a placeholder to .env.example with a comment.
- Read it via import { env } from "@/lib/env". Never process.env.X.
Why emptyStringAsUndefined matters
Hosting platforms (Vercel especially) often set undefined env vars as empty strings. We set emptyStringAsUndefined: true so optional vars don't fail Zod validation. If you add a NEW required string field, an empty value will be treated as missing — which is what you want.
process.env.X is forbidden everywhere except env.ts
Reading process.env.X directly bypasses validation. A typo silently passes. The CLAUDE.md and ESLint rules enforce this — always import { env } from "@/lib/env" so TypeScript and Zod have your back.
SKIP_ENV_VALIDATION for Docker builds
Set SKIP_ENV_VALIDATION=1 to build without Stripe keys (handy for CI Docker images that build before secrets are mounted). The build will succeed; the app will still crash at runtime if real vars are missing. Use sparingly.