How to Build a SaaS with Claude Code (Step by Step)
To build a SaaS with Claude Code, give it three things it cannot guess: a written spec, a CLAUDE.md with your stack and rules, and safe access to a real backend over MCP. Then work in small, reviewable steps (schema, auth, core feature, billing, tests, deploy), keeping every database change in a migration file that you read before it runs. This guide walks through each step with the commands and prompts, using a hosted Postgres backend that Claude Code can inspect without being able to wreck it.
Claude Code facts come from Anthropic's Claude Code docs as of November 2026. Backend steps use StitchDB; most of them work the same on any Supabase-compatible backend.
#What you are building
A small B2B SaaS: users sign up, create a team, invite colleagues, manage "projects" that only their team can see, and pay a monthly subscription. That covers what most SaaS products need on day one:
- Auth (e-mail and Google sign-in)
- Teams with roles and invites
- Data isolated per team
- Subscription billing
- A deploy pipeline with preview environments
#Step 0: tools
Install Claude Code. Anthropic's recommended native install on macOS, Linux and WSL:
curl -fsSL https://claude.ai/install.sh | bash
claude --versionMost surfaces need a Claude subscription or an Anthropic Console account. Then install the StitchDB CLI, which runs the whole backend locally as native processes, without Docker (CLI install):
npm install -g stitchdb-cli
stitchdb loginCreate a free project in the dashboard for staging. Production can come later.
#Step 1: write the spec before the code
Open an empty folder, run claude, and ask for a plan, not code:
We are building a B2B SaaS for tracking client projects. Read SPEC.md. Ask me questions until the spec has no open decisions, then update it. Do not write code yet.
A useful SPEC.md has: who the users are, the five or six screens, the tables with their columns, who can see what, the plans and their limits, and what is out of scope. Ten minutes here saves hours of an agent building the wrong thing confidently.
#Step 2: CLAUDE.md, the rules it follows every session
Claude Code reads CLAUDE.md from the project root at the start of every session. Run /init to generate a first version from the codebase, then add what it cannot discover. Keep it short and specific:
# Stack
- Next.js (App Router), TypeScript, stitchdb-js
- Backend: StitchDB (Postgres, Auth, REST, Storage, Edge Functions)
- Local backend: `stitchdb start`; never use the hosted project for experiments
# Database rules
- Every schema change is a migration: `stitchdb migration new <name>`, then SQL in stitchdb/migrations/
- Every table in public has RLS enabled and policies in the same migration
- Team data has an org_id uuid column; policies use auth.is_member(org_id) / auth.has_role('admin', org_id)
- Never use the service_role key in browser code
- After schema changes run `stitchdb gen types typescript --local > src/database.types.ts`
# Workflow
- Small steps; run `npm test` and `stitchdb db reset` before saying something works
- Show me the migration SQL before applying it to anything but the local databaseCLAUDE.md is context, not enforcement. The guardrails that actually stop mistakes come from the credentials in step 3.
#Step 3: connect the backend safely
Set up the backend folder and start it locally:
stitchdb init
stitchdb link --project-ref <project-ref>
stitchdb startstitchdb start prints a local API URL (http://127.0.0.1:54321), a local Postgres URL, a local Studio and a mail catcher for Auth e-mails (local development). Claude Code can run stitchdb db query --local "..." freely here.
For the hosted project, add the MCP server read-only and scoped to one project:
claude mcp add --transport http stitchdb \
"https://stitchdb.com/platform/mcp?project_ref=<project-ref>&read_only=true" \
--header "Authorization: Bearer sdb_pat_..."Run /mcp to check it is connected (Claude Code setup). Claude can now read logs, advisors and the real schema of staging, but cannot change it. Schema changes reach the hosted project only through stitchdb db push, which lists pending migrations and asks before applying them.
Optional: the StitchDB agent skill teaches Claude Code what StitchDB error codes and empty RLS results mean, and when to look things up in the docs:
npx skills add stitchdb/agent-skills#Step 4: schema and teams
Turn on Authentication › Organizations in Studio. It adds tables for organizations, members and invites, functions such as org_create and org_invite, JWT claims for the active organization, and policy helpers (organizations). Then prompt:
Create a migration for the projects table from SPEC.md. Team-owned via org_id. Members can read, admins can write. Apply it locally, run db reset, and show me the SQL.
Expect something close to this, and read it before you accept it:
create table public.projects (
id bigint generated always as identity primary key,
org_id uuid not null,
name text not null,
status text not null default 'active',
created_at timestamptz not null default now()
);
alter table public.projects enable row level security;
create policy "members read" on public.projects
for select to authenticated
using (auth.is_member(org_id));
create policy "admins write" on public.projects
for all to authenticated
using (auth.has_role('admin', org_id))
with check (auth.has_role('admin', org_id));
create index on public.projects (org_id);Things to check every time: RLS is enabled, there is a with check on writes, no policy uses using (true) for writes, and the foreign-key-like columns have indexes. The full list is in reviewing AI-generated RLS policies.
#Step 5: auth and the first screens
Add sign-up, sign-in with e-mail and password, and Google sign-in. After sign-in, if the user has no organization, show a "create your team" screen. Use stitchdb-js.
The client calls are short:
import { createClient } from 'stitchdb-js'
import type { Database } from './database.types'
export const stitchdb = createClient<Database>(
process.env.NEXT_PUBLIC_STITCHDB_URL!,
process.env.NEXT_PUBLIC_STITCHDB_ANON_KEY!,
)
await stitchdb.auth.signUp({ email, password })
await stitchdb.auth.signInWithOAuth({ provider: 'google', options: { redirectTo: `${origin}/app` } })
const org = await stitchdb.orgs.create({ name: 'Acme', slug: 'acme' })
await stitchdb.orgs.invite(org.id, { email: '[email protected]', role: 'admin' })Locally, every e-mail lands in the mail catcher, so Claude can test the invite flow end to end. Add the Playwright MCP server and ask it to click through sign-up as two different users and confirm each sees only their own team's projects.
#Step 6: billing
Enable Integrations › Stripe in Studio (Pro plan and up). It syncs customers, products, prices, subscriptions and invoices into a stripe schema through webhooks, and adds a helper for policies (Stripe sync). Prompt:
Add a Checkout page for the Pro price using an Edge Function that creates a Stripe Checkout Session with metadata.user_id. Gate the "export" feature on an active subscription using stripe.has_active_subscription. Use Stripe test mode.
The gate itself is one line of SQL in a policy or function:
select stripe.has_active_subscription(auth.uid());Set the Stripe key as a function secret, never in code:
stitchdb secrets set STRIPE_SECRET_KEY=sk_test_...
stitchdb functions deploy create-checkoutMore detail in Stripe subscriptions backend.
#Step 7: review, then ship
Before each merge:
- Ask Claude to run the tests and
stitchdb db reset(rebuilds the local database from migrations and seed data). - Ask it to read Advisors on staging through MCP: tables without RLS, views that bypass RLS, unindexed foreign keys, functions with a mutable
search_path. - Read the diff yourself, migrations first.
For deploys, install the StitchDB GitHub App or run stitchdb init --github to write a GitHub Actions workflow. Each pull request then gets a preview branch with its own database, URL and keys and the PR's migrations applied; merging pushes the migrations to production (GitHub integration). Preview branches are on Pro and up.
Turn on the Block exposing migrations security setting ("enforce": "block" in the project's security settings) so stitchdb db push refuses a migration that leaves a table with RLS off (exposure check).
#Step 8: running it
Once you have users, Claude Code becomes your on-call helper. With the read-only MCP connection:
Why did sign-ups fail between 09:00 and 10:00? Check auth and API logs and the advisors.
When it needs to fix data in production, give it an agent key instead of a read-write token: statements like UPDATE without WHERE wait for your approval, and the whole session can be undone.
#What it costs to start
Claude Code needs a Claude subscription or Console API usage; see Anthropic's pricing page. On the backend side, StitchDB Free covers development (2 projects, 500 MB database, 50,000 MAU). Pro at $15.99 a month as of November 2026 adds daily backups, 7 days of point-in-time recovery, preview branches and Stripe sync (pricing).
#FAQ
#Can Claude Code build a whole SaaS on its own?
It can write most of the code, but it builds what you describe. The spec, the data access rules and the review of every migration are yours. Treat it as a fast developer who needs clear tickets and code review.
#Should Claude Code have write access to my production database?
Not through a general token. Use a read-only MCP connection for production, push schema changes through reviewed migrations, and use an agent key with approvals and undo for the rare data fix.
#What is the best backend for a SaaS built with Claude Code?
One with SQL, auth, an API and a local version Claude can break freely. Postgres with Row Level Security, migrations in the repository and an MCP server with read-only mode is a strong default.
#How do I stop Claude Code from forgetting my rules?
Put them in CLAUDE.md, keep it short, and back the important ones with real limits: read-only credentials, RLS Guard and blocked exposing migrations.
Start your SaaS backend: create a free project and connect it with the Claude Code guide.