Wire up a real backend
If 03-first-run.md ended with pnpm dev painting the placeholder screen, this step rewires the same base against a real Supabase project so that signup and login actually work. Usually 30 minutes to an hour.
If you do not need it yet, you can skip ahead. That said, the data-model decisions waiting after phase 3 (/3-analyze-saas) only mean something once Supabase is in place.
1) Create a Supabase project
Section titled “1) Create a Supabase project”On the supabase.com dashboard, hit New project. Pick a name and a database password. Choose a nearby region. From Korea, Tokyo or Singapore are the safe latency picks. From the US, pick a regional one accordingly.
Once the project is provisioned, head to Settings → API in the left menu. Note three things from that page:
Project URL- The
anon publickey - The
service_rolekey (this one must never end up in client code — more on it below)
While you are there, also note the Project Reference ID (shape: abcdefghijklmn) shown at the top-left. The CLI points at the project by this ID in the next step.
2) supabase link the project
Section titled “2) supabase link the project”From inside your my-saas folder:
supabase login # if you skipped this in [01-prerequisites.md]supabase link --project-ref <project-reference-id>Use the ID you noted above for <project-reference-id>. It also asks for the DB password the first time — the one you picked in step 1.
When the command finishes, a .supabase/ folder appears with the connection info written into it. .gitignore already covers that folder, so leave it as is.
3) Set up .env.local
Section titled “3) Set up .env.local”There is a .env.example at the root of the base. Copy it to .env.local:
cp .env.example .env.localOpen .env.local in an editor and fill in these four lines with your own values:
VITE_SUPABASE_URL=https://<project-ref>.supabase.coVITE_SUPABASE_ANON_KEY=<anon-public-key>VITE_STRIPE_PUBLISHABLE_KEY=pk_test_...VITE_API_MODE=edge-functionsThe VITE_ prefix is what matters. Vite only exposes prefixed variables to the client bundle. Anything without the prefix ends up undefined at build time. That prefix is what blocks something secret like service_role from accidentally shipping to the client.
VITE_API_MODE chooses which mode the API adapter runs in. The default edge-functions is the recommended one. If a need to run on Cloudflare Workers with the Hono adapter ever comes up, switch to hono at that point. The reasoning behind this decision lives in the Expert architecture page. For a beginner, leaving the default is enough.
.env.local should be under .gitignore. Run git status once to confirm that this file does not show up as untracked.
4) Apply the first migration
Section titled “4) Apply the first migration”The migrations the base ships with are skeletons. The api/migrations/ folder has empty seats only; the tables for your SaaS domain (for example, subscriptions, invoices, your own domain entities) get auto-generated by implement-data-layer inside phase 5 (/5-customize-saas). This is one of the spots that fills in when you fork.
For now, confirming that auth and sessions work is enough. Supabase Auth is on from the moment the project exists — no extra migration required.
supabase db pushThis applies every SQL file under api/migrations/ to your Supabase project in order. In the base state, there is barely anything to apply, so the command wraps quickly. No red output means success.
5) Where the RLS policies sit
Section titled “5) Where the RLS policies sit”Every table on Supabase is default deny. A table with RLS on needs a policy in writing before any user — anon key or signed-in — can read or write a row. Without a policy, the table is unreachable.
In this base, every RLS policy is defined inside api/migrations/<timestamp>_<name>.sql. The Supabase Studio UI for creating policies is not used. Policies created in the UI do not end up in git, so they disappear in the next environment.
When your SaaS’s tables and policies get auto-generated in phase 5, SQL of this shape lands in a migration file:
alter table public.subscriptions enable row level security;
create policy "subscriptions_select_own" on public.subscriptions for select to authenticated using (auth.uid() = user_id);That one block enforces “a signed-in user can only select their own rows.” This is one of the spots that fills in when you fork. At the moment you receive the base, your SaaS’s domain tables have not been decided yet, so the actual policies are empty.
6) Verify it works
Section titled “6) Verify it works”Bring pnpm dev back up (Ctrl+C and start it again), and refresh http://localhost:5173 in the browser. The signup screen should now be wired against real Supabase Auth, not the placeholder.
Try a signup with an email and password. In Authentication → Users on the Supabase dashboard, a new row should appear with your email.
If the row does not show, jump to the “Supabase signup will not go through” entry in 09-troubleshooting.md. The most common cause is a wrong value in VITE_SUPABASE_URL or VITE_SUPABASE_ANON_KEY in .env.local.
7) Edge Function secrets — Stripe and service_role
Section titled “7) Edge Function secrets — Stripe and service_role”This seat is different from .env.local in step 3. Secrets that must not ship in the client bundle (Stripe Secret key, Stripe Webhook Secret, the Supabase service_role) get set directly on the Supabase side.
supabase secrets set STRIPE_SECRET_KEY=sk_test_...supabase secrets set STRIPE_WEBHOOK_SECRET=whsec_...supabase secrets listIf supabase secrets list shows both keys, you are set. Inside Edge Functions, they are reached via Deno.env.get('STRIPE_SECRET_KEY'). Unrelated to your .env* files.
The Stripe Webhook Secret is the one you get when you register a webhook endpoint on the Stripe dashboard during implement-billing in phase 5. Until you reach that point, this section can stay empty.
8) One paragraph on the API adapter
Section titled “8) One paragraph on the API adapter”There is an adapter interface in src/shared/api/server/. The default is calling Supabase Edge Functions (edge-functions); the alternative is Hono on Node or Cloudflare Workers (hono). The same feature code does not know which adapter it is calling — it just hits api.invoke('checkout-session', payload).
For a beginner, leaving the default edge-functions is the path of least friction. Supabase holds the DB, Auth, and Edge Functions in one place, so no extra infra layer comes along. If traffic grows later, or infra policy shifts you toward Cloudflare Workers, flipping VITE_API_MODE swaps the adapter without touching a single line of the calling code.
This decision shares its table with the API adapter decision inside phase 4 (/4-design-saas). The pick lives in .claude/state/api-adapter.json.
Where things commonly go wrong
Section titled “Where things commonly go wrong”- 401 or 403: re-copy the
anonkey. A trailing space or a newline often sneaks in during paste. Failed to connect to Supabase: theVITE_SUPABASE_URLin.env.localis missinghttps://or has a trailing/. The exact shape ishttps://<ref>.supabase.cowith nothing after.supabase db pushfails on auth: yoursupabase loginsession expired. Log in again.- Anything else lives in the Supabase section of 09-troubleshooting.md.
One more step — does it actually behave the way I described?
Section titled “One more step — does it actually behave the way I described?”Once you are here, you have seen signup and login hit real Supabase. The next question is usually: “does this actually work the way I sketched it out in plain words?”
You can click through by hand to confirm, but with two or three scenarios in play, the clicking gets old. The base has one tool that automates that check. Inside Claude Code:
/test-saasThis command reads §2 “Use scenarios” inside docs/design/saas-spec.md. The one-line scenarios you wrote there get translated into validation code by the AI — Vitest takes the unit seats, Playwright takes the e2e ones, and supabase test db takes the RLS policy seats.
What you get back is not the raw test output, but a report rewritten in the same scenario language:
Scenarios: 6 total — 5 passed, 1 failed
✓ Signup — new row via email and password, RLS policy passes✓ Login — session issued, /app reachable✓ Pricing page — Pro plan selection starts a Stripe Checkout session✗ Stripe webhook — signature verification failed: secret in .env.local, missing from supabase secrets File: api/functions/stripe-webhook/index.ts:14 Fix at: supabase secrets set STRIPE_WEBHOOK_SECRET=...When a failure shows up, tell the same Claude “fix this” right there. The seat to look at is already in the report — no extra context to type back in.
How /test-saas works under the hood (how scenarios get split into test files, where the files end up, how this differs from the static review-saas) lives on the Expert verification page.
If data is landing where you expected it to, head into the place where the shape of your SaaS gets decided. 05-customize-design.md takes care of branding and design tone. In between, /3-analyze-saas and /4-design-saas lock in the concept, routing, pricing, and data model.