pealdevDOCS
Back to peal.dev

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)

Terminal
# 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)

Terminal
# 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

TypeScript
// 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.