Production SaaS Starter

Multi-Tenant SaaS Platform

Production-oriented B2B SaaS foundation with workspace-based multi-tenancy, Stripe billing, platform admin, RBAC, and i18n. This guide gets you from clone to running locally in under an hour.

Overview

Overview

The Production SaaS Starter is a workspace-based multi-tenant platform — not a minimal demo. It includes real patterns: PostgreSQL RLS, SECURITY DEFINER RPCs, Stripe webhook idempotency, dual RBAC layers, and a service-provider abstraction.

  • Workspaces with memberships, invites, and slug-scoped URLs
  • Stripe subscriptions, usage metering, promo codes, and enterprise deals
  • Platform admin console with audit logs and user/workspace moderation
  • Email/password + OAuth, 2FA, password reset via Resend

Stack: Next.js 16, React 19, Supabase, Stripe, Resend, next-intl with 4 locales.

Setup~3 min

Install dependencies

Install npm packages from the project root before configuring environment variables.

Run this once after cloning the repository:

Terminal
npm install
Prerequisite~5 min

Environment setup

Configure .env.local before database migrations or running the app. Create the file first, then add values section by section as you complete setup below.

Create your local env file

Copy the example file — add Stripe and Resend values in the sections below. Supabase variables are configured in the Supabase setup section.

Terminal
cp .env.example .env.local

App

Required

Your deployed app URL — used for checkout redirects, email links, and OAuth callbacks.

Steps

  1. 1For local dev, set NEXT_PUBLIC_APP_URL=http://localhost:3000
  2. 2For production, set it to your Vercel deployment URL or custom domain
  3. 3Add the variable in Vercel → Project → Settings → Environment Variables

Environment variables

NEXT_PUBLIC_APP_URLRequired

Public URL of your SaaS app (no trailing slash).

Where to find

Local: http://localhost:3000 · Production: your Vercel project domain or custom domain

Example: https://app.yourdomain.com

NEXT_PUBLIC_APP_NAME

Brand name shown in transactional emails.

Where to find

Choose any display name — no external service needed

Example: Acme SaaS

Setup~15 min

Supabase setup

Complete these Supabase steps in order — project creation, environment variables, and database migration.

Complete these Supabase steps in order — project creation, environment variables, and database migration.

7 steps — complete in order

Supabase setup

~5 min

Create your project and copy your Project ID

1

Create a Supabase project

Sign in at supabase.com, click New project, choose a name and region, set a database password, and wait for provisioning to finish.

Open Supabase Dashboard
2

Copy your Project ID

You will use this when linking the CLI and building your API URL.

Where to find it: Supabase Dashboard → Project → Project Settings → Project ID

Supabase environment variables

Add these to .env.local after creating your Supabase project — before running database migrations.

NEXT_PUBLIC_SUPABASE_URL

Your project API URL. Replace YOURPROJECTID with your actual Supabase Project ID.

.env.local
NEXT_PUBLIC_SUPABASE_URL=https://YOURPROJECTID.supabase.co

Where to find it

Supabase Dashboard → Project Settings → API

NEXT_PUBLIC_SUPABASE_ANON_KEY

Public anon key — safe for browser use. Copy from your dashboard; do not use a placeholder value.

Where to find it

Supabase Dashboard → Project Settings → API

SUPABASE_SERVICE_ROLE_KEY

Server-only key. Never expose to the client. Copy from your dashboard; do not use a placeholder value.

Where to find it

Supabase Dashboard → Project Settings → API

Database setup and migrations

~15 min

CLI setup, link, and schema migration

3

Install Supabase CLI

Install globally, then verify:

Terminal
npm install -g supabase

Verify installation:

Terminal
supabase --version
4

Log in to Supabase CLI

A browser window opens for authentication. Sign in and return to the terminal.

Terminal
supabase login
5

Link your local project

Replace PROJECT_REF with your Project ID from step 2.

Terminal
supabase link --project-ref <PROJECT_REF>

Example:

Terminal
supabase link --project-ref abcdefghijklmnop
6

Apply database migrations

Creates all tables, functions, policies, and seed data. Ensure Supabase credentials are in .env.local before continuing.

Terminal
supabase db push
7

Verify tables were created

Confirm your schema is in place before continuing.

Where to find it: Supabase Dashboard → Project → Table Editor

Updating database

After pulling repo updates with database changes:

Terminal
git pull
supabase db push
Configuration~15 min

Stripe setup

Stripe CLI

Use the Stripe CLI for local authentication and webhook forwarding during development.

  1. 1Install Stripe CLI using the instructions from the official Stripe documentation
  2. 2Complete the platform-specific installation steps
  3. 3Run Stripe authentication/login
  4. 4Continue with local Stripe development after authentication
Stripe CLI installation guide

Authenticate the CLI

Run login — a browser window opens to connect your Stripe account:

Terminal
stripe login

Forward webhooks locally

In a separate terminal, forward events to your local webhook handler. Copy the signing secret into STRIPE_WEBHOOK_SECRET:

Terminal
stripe listen --forward-to localhost:3000/api/stripe/webhook

API keys

Copy your keys from the Stripe dashboard into .env.local — never hardcode keys in the repository or documentation.

Where to find them

Stripe Dashboard → Developers → API Keys

  • Publishable Key (pk_test_...)
  • Secret Key (sk_test_...)

Environment variables

STRIPE_SECRET_KEYRequired

Server-side Stripe API key — copy from your dashboard, never hardcode.

Where to find

Stripe Dashboard → Developers → API Keys — Secret key (sk_test_...)

NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYRequired

Client-side key for Stripe.js checkout — copy from your dashboard, never hardcode.

Where to find

Stripe Dashboard → Developers → API Keys — Publishable key (pk_test_...)

Webhooks

Steps

  1. 1For local development, run stripe listen --forward-to localhost:3000/api/stripe/webhook and copy the webhook signing secret
  2. 2For deployed environments, add webhook endpoint: https://YOUR_APP_URL/api/stripe/webhook
  3. 3Select events: checkout.session.completed, customer.subscription.*, invoice.*, payment_method.*
  4. 4Copy the webhook signing secret to STRIPE_WEBHOOK_SECRET in .env.local

Environment variables

STRIPE_WEBHOOK_SECRETRequired

Verifies webhook signatures from Stripe.

Where to find

Stripe Dashboard → Developers → Webhooks → Signing secret · Local dev: output of stripe listen

Configuration~10 min

Resend setup

Resend

Required

Transactional email — invites, 2FA OTP, password reset. Supabase built-in email is not used.

Steps

  1. 1Sign up at resend.com
  2. 2Add and verify your sending domain (DNS records)
  3. 3Create an API key
  4. 4Set RESEND_FROM_EMAIL to a verified address on your domain

Environment variables

RESEND_API_KEYRequired

API key for sending emails.

Where to find

Resend Dashboard → API Keys → Create API Key

RESEND_FROM_EMAILRequired

Verified sender address (e.g. noreply@yourdomain.com).

Where to find

Resend Dashboard → Domains → Add domain → verify DNS → use an address on that domain

Setup~2 min

Quick start

After completing environment, Supabase, Stripe, and Resend setup above, run these commands in order:

Install project dependencies

Terminal
npm install

Sync seeded plans to Stripe — mainly for testing and keeping Stripe plans in sync with the database

Terminal
npm run sync:plans

Start dev server at http://localhost:3000

Terminal
npm run dev
Reference~3 min

Development vs production

Use different commands for day-to-day development and for previewing a production build locally.

Development

Start the development server with hot reload:

Terminal
npm run dev

Production preview

Preview the production environment locally — build the app, then start the production server:

Terminal
npm run build
Terminal
npm run start
Reference

Scripts reference

CommandDescription
supabase db pushApply database schema (supabase db push)
npm run sync:plansSync seeded plans to Stripe products/prices
npm run devStart development server
npm run buildProduction build
npm run startStart production server (after build)
npm run testRun Jest unit tests (services only)
Reference

Architecture

Four layers with strict call order — agents and developers follow the same path:

UI → Server Actions / API → Services → Repositories → PostgreSQL (RLS/RPC)

Key locations:

1

Layer 1

Presentation — src/app/, src/components/

2

Layer 2

Application — server actions, createRequestContext(), RBAC guards

3

Layer 3

Domain — src/modules/*/ *.service.ts (business rules)

4

Layer 4

Data — repositories + PostgreSQL RLS/RPCs via src/services/

Reference

Multi-tenancy

A workspace is the tenant — there is no separate organization table.

  • Tenant key: workspaces.id (UUID); URLs use unique slug
  • Memberships link users to workspaces with owner, admin, or member roles
  • RLS policies enforce workspace_id IN (user's memberships)

Platform admins access /admin via platform_admins table with super_admin, platform_admin, or platform_viewer roles.

Reference

Authentication & authorization

Auth flow: login → optional 2FA → onboarding (no workspace) or workspace dashboard. Platform admin invites route to platform-onboarding.

  • Email/password and OAuth via Supabase Auth
  • 2FA via email OTP (Resend) — not Supabase built-in email
  • Password reset with token-based flow and rate limiting

Workspace roles and permissions:

  • ownerbilling, delete workspace, full member management
  • admininvite/remove members, update workspace settings
  • memberread and participate — no admin actions
Reference

Billing & subscriptions

One active subscription per workspace, tied to Stripe customer and subscription IDs.

  • Checkout for new subscriptions; portal for self-serve management
  • Upgrades with proration; downgrades scheduled at period end
  • Seeded plans: Starter ($29/mo), Pro ($39/mo), Enterprise (custom)
  • Webhook at /api/stripe/webhook syncs state with idempotent stripe_events table
Deployment~15 min

Deployment

Recommended stack: Vercel (Next.js) + Supabase Cloud + Stripe webhooks.

  1. 1Apply schema to production Supabase (supabase db push)
  2. 2Set all env vars in Vercel — use the Environment setup section above
  3. 3Deploy the Next.js app
  4. 4Register Stripe webhook → https://YOUR_APP_URL/api/stripe/webhook
  5. 5Bootstrap super admin and run sync:plans in production