Sabo uses environment variables to configure integrations with Supabase, Stripe, PostHog, and other services. This guide provides a complete reference of all variables, their purpose, and how to set them up.
Overview
Environment variables are key-value pairs that configure your application without hardcoding sensitive information. Sabo uses three types of environment files:.env.local- Local development (not committed to git).env.test- Testing environment (Playwright E2E tests)- Production environment - Set in your hosting platform (Vercel, Netlify, etc.)
Variable Naming Convention
Sabo follows Next.js environment variable conventions:NEXT_PUBLIC_*- Exposed to the browser (client-side code can access these)- Without
NEXT_PUBLIC_- Server-side only (API routes, server components)
Client-side code cannot access server-only variables. If you try to access
process.env.STRIPE_SECRET_KEY in a client component, it will be undefined.Required Variables
These variables are essential for Sabo to function. Your app will not work without them.Core Application
The base URL of your application. Used for generating absolute URLs, OAuth redirects, and email links.Local development:Production:Used in:
- OAuth callback URLs (
src/app/(auth)/actions.ts) - Email verification links
- Stripe Customer Portal return URL (
src/app/api/customer_portal/route.ts)
Supabase (Authentication & Database)
Your Supabase project URL. Find this in your Supabase Dashboard → Project Settings → API.Format:Used in:
- Browser client (
src/lib/supabase/client.ts) - Server client (
src/lib/supabase/server.ts) - Middleware (
src/lib/supabase/middleware.ts) - Test helpers (
tests/e2e/helpers/auth.ts)
Your Supabase project’s anonymous key (also called “anon key”). This is safe to expose to the browser.Format:Used in:
- Browser client (
src/lib/supabase/client.ts) - Server client (
src/lib/supabase/server.ts) - Middleware (
src/lib/supabase/middleware.ts) - Test helpers (
tests/e2e/helpers/auth.ts)
anon publicThis key is safe to expose in client-side code. It respects Row Level Security (RLS) policies.
Your Supabase project’s secret key. This bypasses Row Level Security and should never be exposed to the browser.Format:Used in:
- Service client for bypassing RLS (
src/lib/supabase/server.ts-createServiceClient()) - Stripe webhook handler (
src/app/api/webhooks/stripe/route.ts) - Polar webhook handler (
src/app/api/webhooks/polar/route.ts)
service_role secretStripe (Payments)
Your Stripe secret API key. Used to create checkout sessions, manage subscriptions, and access the Stripe API.Test mode:Production mode:Used in:
- Stripe client initialization (
src/lib/payments/stripe.ts) - Creating checkout sessions (
src/app/api/checkout_sessions/route.ts) - Creating Customer Portal sessions (
src/app/api/customer_portal/route.ts) - Webhook verification (
src/app/api/webhooks/stripe/route.ts)
The webhook signing secret for verifying Stripe webhook events. Ensures webhook requests actually come from Stripe.Local development (Stripe CLI):Production:Used in:
- Webhook signature verification (
src/app/api/webhooks/stripe/route.ts)
- Local Development
- Production
- Install Stripe CLI:
brew install stripe/stripe-cli/stripe - Login:
stripe login - Forward webhooks:
stripe listen --forward-to localhost:3000/api/webhooks/stripe - Copy the
whsec_...secret from the terminal output
Stripe CLI automatically forwards webhook events to your local server and provides a test signing secret.
Your Stripe publishable API key. Safe to expose to the browser. Used for Stripe Checkout and payment forms.Test mode:Production mode:Used in:
- Client-side Stripe initialization
- Stripe Checkout redirects
Polar (Payments)
Server-side token used by Polar Checkout, Customer Portal, and webhook helpers.Format:Used in:
- Checkout redirect handler (
src/app/api/checkout/route.ts) - Customer portal handler (
src/app/api/portal/route.ts) - Webhook handler + invoice helper (
src/app/api/webhooks/polar/route.ts)
Signing secret used to verify Polar webhook payloads.Format:How to get:
- Polar Dashboard → Settings → Webhooks → Add endpoint.
- Point to
/api/webhooks/polar(ngrok URL in dev, production domain in prod). - Copy the generated secret.
src/app/api/webhooks/polar/route.tsWhenever you recreate the webhook endpoint (new ngrok URL or production domain), update this secret and restart the server.
Optional Variables
These variables enable additional features but are not required for basic functionality.Stripe (Advanced)
The Stripe Price ID for your Pro plan monthly subscription.Format:Used in:
- Plan configuration (
src/lib/payments/plans.ts) - Pricing page for displaying correct checkout button
- Create a product in Stripe Dashboard → Products
- Add a monthly recurring price
- Copy the Price ID (starts with
price_)
If not set, the value from
src/lib/payments/plans.ts is used as fallback. You can hardcode Price IDs in that file instead of using environment variables.The Stripe Price ID for your Pro plan yearly subscription.Format:Used in:
- Plan configuration (
src/lib/payments/plans.ts) - Pricing page billing cycle toggle
The configuration ID for your Stripe Customer Portal. Allows customization of which features customers can manage.Format:Used in:
- Customer Portal session creation (
src/app/api/customer_portal/route.ts)
- Go to Stripe Dashboard → Settings → Billing → Customer portal
- Create a new configuration or use the default
- Copy the configuration ID
- Which payment methods customers can add/remove
- Whether customers can cancel subscriptions
- Custom branding and messaging
Polar (Advanced)
Polar product ID for the monthly Pro tier. Rendered in the pricing UI and appended to Used in: Plan configuration + pricing checkout (
/api/checkout.Format:src/lib/payments/plans.ts, src/components/marketing/pricing.tsx)Polar product ID for the yearly Pro tier.Format:Used in:
src/lib/payments/plans.ts, pricing toggle, webhook plan lookup helper.Controls whether helper utilities call the sandbox or production Polar API (Used in:
https://sandbox-api.polar.sh vs https://api.polar.sh).- Webhook invoice helper (
src/app/api/webhooks/polar/route.ts) — determines API base URL - Checkout handler — when modified to use environment-driven server selection
- Customer portal (
src/app/api/portal/route.ts) — usesNODE_ENVby default, but can be updated to respectPOLAR_SANDBOX
The default boilerplate ships with
server: "sandbox" hardcoded in /api/checkout for safety. To make it environment-driven, update the handler to use process.env.POLAR_SANDBOX === "true" ? "sandbox" : "production". See the Payments with Polar guide for details.PostHog (Analytics)
Your PostHog project API key. Required to enable PostHog analytics.Format:Used in:
- Client-side PostHog initialization (
src/components/posthog-provider.tsx) - Server-side PostHog client (
src/lib/posthog/server.ts)
PostHog is completely optional. If this variable is not set, Sabo will function normally without analytics. Both
NEXT_PUBLIC_POSTHOG_KEY and NEXT_PUBLIC_POSTHOG_HOST must be set for PostHog to activate.The PostHog host URL. Depends on your PostHog hosting choice.PostHog Cloud (US):PostHog Cloud (EU):Self-hosted:Used in:
- Client-side PostHog initialization (
src/components/posthog-provider.tsx) - Server-side PostHog client (
src/lib/posthog/server.ts)
Testing (Playwright)
Email address of a test user for E2E authentication tests.Format:Used in:
- Authentication helper (
tests/e2e/helpers/auth.ts) - Sign-in tests (
tests/e2e/auth/sign-in.spec.ts) - Password reset tests (
tests/e2e/auth/password-reset.spec.ts)
- Create a test user in your Supabase database (manually or via Supabase Dashboard)
- Use a dedicated test email (not a real user account)
- Add email to
.env.testfile
Password for the test user account.Format:Used in:
- Authentication helper (
tests/e2e/helpers/auth.ts) - Sign-in tests (
tests/e2e/auth/sign-in.spec.ts)
System Variables
The Node.js environment. Automatically set by Next.js and hosting platforms.Values:
development- Local dev server (pnpm dev)production- Production build (pnpm build && pnpm start)test- Testing environment (set by testing frameworks)
- Auth callback redirects (
src/app/auth/callback/route.ts) - PostHog debug mode (
src/components/posthog-provider.tsx) - Conditional behavior based on environment
You don’t need to set this manually. Next.js and hosting platforms handle it automatically.
Indicates whether the code is running in a CI/CD environment.Used in:
- Playwright configuration (
playwright.config.ts)- Enables
forbidOnly(fails iftest.only()is left in code) - Sets
retries: 2(retries flaky tests twice) - Sets
workers: 1(sequential test execution) - Disables
reuseExistingServer(always starts fresh server)
- Enables
Automatically set by CI platforms like GitHub Actions, GitLab CI, CircleCI, etc. You don’t need to set this manually.
Setup Guide
Local Development Setup
Create .env.local file
Copy the template below and replace placeholders with your actual values:
.env.local
Restart your dev server (
pnpm dev) after creating or modifying .env.local.Production Setup (Vercel)
Deploy to Vercel
- Push your code to GitHub
- Import repository in Vercel
- Vercel will detect Next.js automatically
Add environment variables
In Vercel Dashboard → Your Project → Settings → Environment Variables, add:
Update Supabase URLs
In Supabase Dashboard → Authentication → URL Configuration:
- Site URL:
https://yourdomain.com - Redirect URLs:
https://yourdomain.com/auth/callbackhttps://*.vercel.app/auth/callback(for preview deployments)
Vercel preview deployments get unique URLs. Use wildcard patterns to allow preview authentication.
Set up production Stripe webhook
- Go to Stripe Dashboard → Developers → Webhooks
- Click “Add endpoint”
- Endpoint URL:
https://yourdomain.com/api/webhooks/stripe - Select events:
checkout.session.completedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.paidinvoice.payment_failed
- Copy signing secret and add to Vercel as
STRIPE_WEBHOOK_SECRET
Set up production Polar webhook
- Polar Dashboard → Settings → Webhooks → Add endpoint.
- Endpoint URL:
https://yourdomain.com/api/webhooks/polar. - Subscribe to subscription + order events.
- Copy the signing secret into Vercel as
POLAR_WEBHOOK_SECRET. - Ensure
POLAR_SANDBOX=falseso/api/checkoutand the webhook use live APIs.
For a complete deployment checklist (build command, server config, secrets), refer to the Vercel deployment guide.
Troubleshooting
Environment variables not working
Environment variables not working
Symptoms: Variables are
undefined in code, features not working.Causes:- Server not restarted after changing
.env.local - Variable name typo
- Trying to access server-only variable in client code
.env.localfile in wrong directory
- Restart dev server: Stop (
Ctrl+C) and restart (pnpm dev) - Check spelling: Variable names are case-sensitive
- Check client/server: Only
NEXT_PUBLIC_*variables work in browser - Verify location:
.env.localmust be in project root (same directory aspackage.json)
'NEXT_PUBLIC_SUPABASE_URL is not defined' error
'NEXT_PUBLIC_SUPABASE_URL is not defined' error
Cause: Supabase environment variables not set or server not restarted.Fix:
- Check
.env.localhas bothNEXT_PUBLIC_SUPABASE_URLandNEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY - Restart dev server:
pnpm dev - Verify no typos in variable names (common:
ANON_KEYvsPUBLISHABLE_KEY)
PUBLISHABLE_KEY not ANON_KEY:Stripe webhook signature verification failed
Stripe webhook signature verification failed
Cause: Incorrect
STRIPE_WEBHOOK_SECRET or webhook not from Stripe.Fix:Local development:- Ensure Stripe CLI is running:
stripe listen --forward-to localhost:3000/api/webhooks/stripe - Copy the
whsec_...secret from terminal output - Update
STRIPE_WEBHOOK_SECRETin.env.local - Restart server
- Verify webhook endpoint URL matches exactly:
https://yourdomain.com/api/webhooks/stripe - Check webhook signing secret in Stripe Dashboard → Webhooks → [Your endpoint] → Signing secret
- Update
STRIPE_WEBHOOK_SECRETin Vercel - Redeploy
Polar webhook signature verification failed
Polar webhook signature verification failed
Cause:
POLAR_WEBHOOK_SECRET doesn’t match the endpoint configured in Polar Dashboard, or the webhook still points to an old URL (common when ngrok restarts).Fix:- Reconfirm the webhook endpoint URL in Polar Dashboard → Settings → Webhooks.
- Copy the latest signing secret and update
POLAR_WEBHOOK_SECRET(.env.localfor dev, hosting env for prod). - Restart the dev server or redeploy.
- Trigger a sandbox checkout to emit
subscription.created/order.paidevents and ensure the terminal logs show200 OK.
OAuth redirect not working
OAuth redirect not working
Cause:
NEXT_PUBLIC_SITE_URL doesn’t match Supabase configuration.Fix:-
Check
NEXT_PUBLIC_SITE_URLin.env.local: -
Ensure it matches Supabase Dashboard → Authentication → URL Configuration:
- Site URL must match
NEXT_PUBLIC_SITE_URLexactly - Redirect URLs must include
{NEXT_PUBLIC_SITE_URL}/auth/callback
- Site URL must match
- No trailing slash in URL
http://localhost:3000/❌ (trailing slash)http://localhost:3000✅
PostHog not tracking events
PostHog not tracking events
Cause: Missing or incorrect PostHog environment variables.Fix:
-
Verify both variables are set:
-
Check PostHog key format starts with
phc_ - Restart dev server
-
Open browser console and look for
[PostHog]debug messages (in development mode) - Disable ad blockers (they often block PostHog)
PostHog only activates when both
NEXT_PUBLIC_POSTHOG_KEY and NEXT_PUBLIC_POSTHOG_HOST are set. If either is missing, PostHog is disabled.Playwright tests can't authenticate
Playwright tests can't authenticate
Cause: Missing test credentials or incorrect
.env.test setup.Fix:-
Create
.env.testin project root: -
Create test user in Supabase:
- Go to Supabase Dashboard → Authentication → Users
- Add user manually or via SQL
- Verify test user exists and email is confirmed
-
Run tests:
pnpm test:e2e
Security Best Practices
1. Never commit secrets to version control
1. Never commit secrets to version control
What to do:
- Add
.env.local,.env.test,.env*.localto.gitignore - Use environment variables, not hardcoded secrets
- Review commits before pushing to ensure no secrets leaked
- Rotate all compromised keys immediately
- Remove from git history:
git filter-branchor use BFG Repo-Cleaner - Force push to remote (if safe to do so)
2. Use different keys for development and production
2. Use different keys for development and production
Why: Prevents test data from mixing with production data and limits blast radius if dev keys are compromised.Setup:
- Stripe: Use test keys (
sk_test_,pk_test_) in development - Supabase: Use separate projects for dev and prod (recommended)
- PostHog: Use separate projects or test mode
3. Restrict server-only variables
3. Restrict server-only variables
Server-only variables (never prefix with NEXT_PUBLIC_):
SUPABASE_SECRET_KEY- Bypasses Row Level SecuritySTRIPE_SECRET_KEY- Full Stripe account accessSTRIPE_WEBHOOK_SECRET- Webhook verificationPOLAR_ACCESS_TOKEN- Full Polar API accessPOLAR_WEBHOOK_SECRET- Polar webhook verification
NEXT_PUBLIC_* variables in browser DevTools.Check your code:4. Rotate keys regularly
4. Rotate keys regularly
Recommended schedule:
- Production secrets: Every 90 days
- After team member departure: Immediately
- After suspected breach: Immediately
- Generate new secret key in Supabase Dashboard → Project Settings → API
- Update
SUPABASE_SECRET_KEYin hosting platform - Redeploy
- Revoke old key
- Create new secret key in Stripe Dashboard → Developers → API keys
- Update
STRIPE_SECRET_KEYin hosting platform - Redeploy
- Delete old key from Stripe Dashboard
5. Use environment-specific URLs
5. Use environment-specific URLs
Why: Prevents production webhooks from hitting development servers and vice versa.Setup:
Quick Reference
All Variables Summary
| Variable | Access | Required? | Where Used |
|---|---|---|---|
NEXT_PUBLIC_SITE_URL | Public | Yes | OAuth redirects, email links, Stripe portal |
NEXT_PUBLIC_SUPABASE_URL | Public | Yes | All Supabase clients |
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY | Public | Yes | All Supabase clients |
SUPABASE_SECRET_KEY | Secret | Yes | Service client, webhooks |
STRIPE_SECRET_KEY | Secret | Yes | Stripe API, checkout, portal |
STRIPE_WEBHOOK_SECRET | Secret | Yes | Webhook verification |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | Public | Yes | Client-side Stripe |
NEXT_PUBLIC_STRIPE_PRICE_ID_PRO_MONTHLY | Public | Optional | Pricing page |
NEXT_PUBLIC_STRIPE_PRICE_ID_PRO_YEARLY | Public | Optional | Pricing page |
STRIPE_CUSTOMER_PORTAL_CONFIG_ID | Secret | Optional | Customer portal customization |
POLAR_ACCESS_TOKEN | Secret | Yes | Polar checkout, portal, webhooks |
POLAR_WEBHOOK_SECRET | Secret | Yes | Polar webhook verification |
NEXT_PUBLIC_POLAR_PRODUCT_ID_PRO_MONTHLY | Public | Optional | Polar plan lookup + checkout |
NEXT_PUBLIC_POLAR_PRODUCT_ID_PRO_YEARLY | Public | Optional | Polar plan lookup + checkout |
POLAR_SANDBOX | Secret | Optional | Toggle sandbox vs production Polar API |
NEXT_PUBLIC_POSTHOG_KEY | Public | Optional | PostHog analytics |
NEXT_PUBLIC_POSTHOG_HOST | Public | Optional | PostHog analytics |
TEST_USER_EMAIL | Secret | Optional | Playwright tests |
TEST_USER_PASSWORD | Secret | Optional | Playwright tests |
NODE_ENV | System | Auto | Environment detection |
CI | System | Auto | CI/CD behavior |