Configuration
ShipCommerce is configured entirely through environment variables. This page lists every variable the product reads, what it does, where the value comes from, and whether you can skip it.
The setup wizard (recommended)
You don't have to write .env.local by hand. From a local clone, run:
npm run setupThe wizard walks you through the required values, generates a CRON_SECRET for you if you leave it blank, and writes .env.local. Everything optional — Stripe, email, AI, analytics — can be skipped and added later by re-running the wizard or editing the file.
Prefer editing by hand? Copy .env.example to .env.local and fill in the values from the tables below. The file is annotated with the same links.
Required variables
The store will not run without these seven. Everything else on this page is optional.
# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=
SUPABASE_DB_URL=
# Site
NEXT_PUBLIC_SITE_NAME=My Store
NEXT_PUBLIC_SITE_URL=http://localhost:3000
# Cron (required in production)
CRON_SECRET=| Variable | What it does | Where to get it |
|---|---|---|
| NEXT_PUBLIC_SUPABASE_URL | Your Supabase project endpoint. Used by browser and server. | Supabase → Project Settings → API → Project URL |
| NEXT_PUBLIC_SUPABASE_ANON_KEY | Public key for browser requests. Safe to expose; Row Level Security applies. | Supabase → Project Settings → API → anon public |
| SUPABASE_SERVICE_ROLE_KEY | Server-only key for admin operations. Bypasses Row Level Security. | Supabase → Project Settings → API → service_role |
| SUPABASE_DB_URL | Direct Postgres connection. Used only by db:setup and migrate. | Supabase → Project Settings → Database → Connection string → URI (transaction mode) |
| NEXT_PUBLIC_SITE_NAME | Store name shown in the UI, page titles, and emails. | You choose it |
| NEXT_PUBLIC_SITE_URL | Public base URL. Used for links, redirects, and webhook targets. | http://localhost:3000 locally, your domain in production |
| SITE_NOINDEX | Set to true on a deployment that must stay out of search engines — a staging copy, a preview. Adds a noindex header and meta tag to every page and stops advertising the sitemaps. Leave it unset on the live store. | Optional. Unset by default. |
| CRON_SECRET | Protects the /api/cron/* endpoints. Required in production. | Generate one: openssl rand -hex 32 |
Security: Never expose SUPABASE_SERVICE_ROLE_KEY or SUPABASE_DB_URL on the client side. Only variables prefixed with NEXT_PUBLIC_ are sent to the browser — that prefix is the whole distinction, so never add it to a secret.
Optional variables
Each group below is independent. Leave a group empty and the matching feature stays off — nothing else breaks.
Payments (Stripe)
Stripe is optional. Cash on delivery and bank transfer work out of the box, so you can launch a working store with Supabase alone and add card payments later.
| Variable | What it does | Where to get it |
|---|---|---|
| NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | Public key used to mount the card form in the browser. | Stripe Dashboard → Developers → API keys |
| STRIPE_SECRET_KEY | Server key used to create and capture payments. | Stripe Dashboard → Developers → API keys |
| STRIPE_WEBHOOK_SECRET | Verifies incoming webhooks. Without it, orders are never marked paid. | Written for you by npm run stripe:setup, or printed by npm run stripe:listen locally |
Email (Resend)
| Variable | What it does | Where to get it |
|---|---|---|
| RESEND_API_KEY | Sends order confirmations and marketing email. Unset = no email is sent. | resend.com/api-keys |
| RESEND_FROM_EMAIL | The From address on outgoing mail. | Defaults to onboarding@resend.dev; use an address on a domain you verified in Resend for production |
Uploads (Vercel Blob)
| Variable | What it does | Where to get it |
|---|---|---|
| BLOB_READ_WRITE_TOKEN | Serves product images from Vercel Blob. Optional — when unset, uploads fall back to Supabase Storage, which works but is slower. | Vercel Dashboard → Storage → Blob → Create token |
AI content generation
| Variable | What it does | Where to get it |
|---|---|---|
| ANTHROPIC_API_KEY | Powers AI content generation in the admin panel. | console.anthropic.com |
| OPENAI_API_KEY | Alternative provider for AI content generation. | platform.openai.com/api-keys |
Analytics
| Variable | What it does | Where to get it |
|---|---|---|
| NEXT_PUBLIC_GA_MEASUREMENT_ID | Enables Google Analytics 4. Leave empty to disable. | Google Analytics → Admin → Property settings |
| NEXT_PUBLIC_GTM_ID | Enables Google Tag Manager. Leave empty to disable. | tagmanager.google.com |
| NEXT_PUBLIC_PLAUSIBLE_DOMAIN | Enables Plausible, a privacy-friendly alternative. Leave empty to disable. | plausible.io — the domain you registered there |
Anti-bot (Cloudflare Turnstile)
| Variable | What it does | Where to get it |
|---|---|---|
| NEXT_PUBLIC_TURNSTILE_SITE_KEY | Renders the Turnstile challenge on login and signup forms. | Cloudflare Dashboard → Turnstile |
| TURNSTILE_SECRET_KEY | Verifies the challenge server-side. Set both keys or neither. | Cloudflare Dashboard → Turnstile |
Integrations
| Variable | What it does | Where to get it |
|---|---|---|
| ORDER_WEBHOOK_URL | POSTs every new order as JSON to this URL — Zapier, Make, n8n, Slack, Discord, or your own backend. Unset, the built-in plugin is a silent no-op. | The inbound webhook URL of whichever service you want to pipe orders into |
Privacy notice: the order webhook payload contains the customer's email address. If you forward it to a third-party SaaS, you are exporting personal data under GDPR/CCPA — make sure your privacy policy and processor agreements cover it.
Admin
| Variable | What it does | Where to get it |
|---|---|---|
| ADMIN_IP_WHITELIST | Comma-separated CIDR ranges allowed to reach /admin. Empty means any IP. | Your own office or VPN ranges |
Supabase setup
- Create a project
Go to supabase.com/dashboard and click "New Project".
- Copy the API keys
Project Settings → API:
- Project URL →
NEXT_PUBLIC_SUPABASE_URL - anon public →
NEXT_PUBLIC_SUPABASE_ANON_KEY - service_role →
SUPABASE_SERVICE_ROLE_KEY
- Project URL →
- Copy the connection string
Project Settings → Database → Connection string → URI (transaction mode) →
SUPABASE_DB_URL. - Create the schema
With those four values in
.env.local, run:npm run db:setupThis creates the tables, functions, Row Level Security policies, and the storage bucket. It is idempotent — re-running it is safe, and it is also how you apply new migrations after pulling an update.
Prefer running the SQL yourself? The migrations live in supabase/migrations/. Paste 00000000000000_baseline.sql into the Supabase SQL Editor and run it, then do the same with 00000000000001_storage.sql, in that order.
Stripe setup
Skip this section entirely if you are launching with cash on delivery and bank transfer. You can add Stripe at any time afterwards.
- Copy your API keys
Go to the Stripe Dashboard → API keys. Stripe has two independent sets of keys, and the toggle in the dashboard decides which you are looking at:
- Test mode — keys start with
pk_test_/sk_test_. No real money moves. Use these while you build. - Live mode — keys start with
pk_live_/sk_live_. Real charges. Use these only in production.
Test and live keys are never interchangeable, and neither are their webhook secrets — switching modes means replacing all three Stripe variables.
- Test mode — keys start with
- Register the webhook (deployed sites)
Once your site is live at
NEXT_PUBLIC_SITE_URL, run:npm run stripe:setupThis creates the webhook endpoint with the correct events and appends the resulting
whsec_...secret to.env.local. It is idempotent — it reuses an existing endpoint with the same URL rather than creating duplicates. Copy the secret into your hosting provider's environment variables too. - Forward webhooks locally
Stripe cannot reach
localhost, so don't usestripe:setupduring local development. Use the Stripe CLI instead:# Install the Stripe CLI (macOS) brew install stripe/stripe-cli/stripe stripe login npm run stripe:listenLeave it running in its own terminal. It prints a
whsec_...secret — paste that intoSTRIPE_WEBHOOK_SECRETin.env.localand restartnpm run dev.
You do not create products in Stripe. Your catalogue lives in the ShipCommerce admin panel and prices are sent to Stripe per payment, so there are no Stripe Price IDs to copy anywhere. See Products.
Resend setup
- Create an API key
Sign up at resend.com and create a key →
RESEND_API_KEY. - Verify your domain (production)
Add the DNS records Resend gives you, then set
RESEND_FROM_EMAILto an address on that domain. Until you do, the defaultonboarding@resend.devsender works for testing only.
Verify your configuration
Start the site and walk through a purchase:
npm run dev
# Then check:
# - Sign up a new user
# - Add a product to the cart
# - Place an order with cash on delivery
# - If Stripe is configured, pay with test card 4242 4242 4242 4242
# (any future expiry, any CVC)Before going live, npm run post-deploy checks that the critical variables are present and warns you if your Stripe keys are still in test mode.