Supabase Workflow¶
End-to-end guide for working with Supabase as a Rose developer. Pick a path, follow it.
Pick your environment¶
| Target | Command to start | When |
|---|---|---|
| Local Supabase (Docker) | cd supabase && just dev |
Frontend dev, isolated tests, no prod credentials needed. |
| Preview branch (remote) | ./bootstrap.py --branch |
Migrations / RLS / RPC / MV work. Real Postgres, isolated DB, auto-deleted on PR close. |
| Staging | Already wired via frontend/.env.staging |
Final integration testing before merge. |
You never need to develop against production directly. Migrations reach prod via the branch merge workflow.
Standard workflows¶
A. Pure frontend or backend change (no schema work)¶
# Start once
cd supabase && just dev # Boots local Postgres + Auth + Studio at 127.0.0.1:54323
cd ../supabase && just seed-local # Synthetic data (or seed-local-copy for real)
# Develop
cd ../frontend && just dev
cd ../backend && just dev
Login as admin@admin.com / admin. See Supabase Seeding for the data set you'll see.
B. Schema change (migration, RLS, RPC, materialized view)¶
# 1. Spin up an isolated branch DB
./bootstrap.py --branch
# 2. Write the migration
date +%Y%m%d%H%M%S # generate timestamp
$EDITOR supabase/migrations/20260520143000_my_change.sql
# 3. Apply it to the branch DB
cd supabase
set -a; . ../backend/.env.local; set +a
DB_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
supabase db push --db-url "$DB_URL" --include-all --yes
# OR shortcut that also re-seeds + refreshes MVs:
just seed-branch
# 4. Regenerate frontend types
cd ../frontend && just gen-types
# (commit the generated frontend/shared/src/types/database.generated.ts)
# 5. Type-check + test
cd ../backend && just mypy <changed-files>
cd ../frontend && just lint
Migration is automatically merged into production when the PR closes (via .github/workflows/supabase-branch-cleanup.yml).
C. Reproducing a real client bug¶
The synthetic data won't match real production shapes. Use the *-copy seed path:
# On a preview branch (recommended — isolated, won't pollute local DB)
./bootstrap.py --branch
cd supabase && just seed-branch-copy --clear
# OR against local Supabase
cd supabase && just seed-local-copy --clear
Requires backend/.env.production (downloaded by just download-env all). The copy is ~60 s and contains real visitor IPs / emails / chat content — treat the resulting DB as sensitive.
D. Editing a materialized view or RPC¶
MVs and functions follow the "live remote schema is the only source of truth" rule (see supabase/AGENTS.md):
-- Inspect the CURRENT shape on develop (the source of truth):
SELECT pg_get_viewdef('public.mv_client_stats_30d'::regclass);
SELECT pg_get_functiondef('public.get_account_stats_with_intent'::regproc);
Then write a migration that drops + recreates (or uses CREATE OR REPLACE only if the signature is identical — Postgres rejects return-type changes via CREATE OR REPLACE alone, see 20260318103855 for the fix pattern).
MVs are populated by triggers / cron in production. On a fresh branch, the synthetic seed refreshes them in dependency order (mv_widget_visitors → mv_conversation_visitors → mv_client_stats_30d). If you add a new MV, append its REFRESH to supabase/justfile in the seed-local and seed-branch targets.
Don'ts¶
Don't push to production directly
- Never
supabase db pushagainst the prod project ref (drtzxyuvppalvgczwhne) from your laptop. Migrations reach prod via the branch-merge workflow only. - Never
supabase migration repairagainst prod from your laptop. - Never edit a migration that has already been applied to prod, except to make it idempotent for fresh replays (e.g. adding
DROP FUNCTION IF EXISTSbefore aCREATE OR REPLACE). Develop'sschema_migrationsledger prevents re-application, so the edit only affects new replays.
Don't seed against the wrong target
The 5 seed targets are easy to confuse. Quick reference:
| Command | Hits |
|---|---|
just seed-local |
Local Supabase :54322 |
just seed-local-fresh |
Local Supabase :54322 (--clear first) |
just seed-local-copy |
Local Supabase :54322 |
just seed-branch |
Whatever's in backend/.env.local (your preview branch) |
just seed-branch-copy |
Whatever's in backend/.env.local (your preview branch) |
Debugging¶
"Invalid login credentials" on the backoffice¶
seed.sql didn't run on this DB. Either you skipped just seed / just seed-branch, or the branch is stuck in CREATING_PROJECT / MIGRATIONS_FAILED. See Common errors.
"Access Restricted: admin@admin.com does not have access"¶
Browser holds a stale JWT signed by a previous (deleted) branch's secret. Clear site data for the backoffice origin and log in again.
Backoffice pages show zero data¶
- Conversations / Visitors / Accounts empty → check
environmentfilter (defaultproduction) and the domain selector. Synthetic data is on the 4 fake domains (acme.com,contoso.com,fabrikam.com,demo.local). - Home dashboard tiles show 0 → materialized views aren't refreshed. Re-run
just seed-branch(orREFRESH MATERIALIZED VIEWmanually via Studio in the dependency order documented in seeding).
RLS denying queries you expect to work¶
Most tables gate on has_domain_access(site_domain). Verify:
Admins bypass via is_backoffice_admin(). Non-admins need a row in backoffice_user_domains. The synthetic seed grants user@user.com access to all 4 fake domains.
Cheat sheet¶
# Create / refresh isolated branch DB
./bootstrap.py --branch
# Seed (all idempotent)
cd supabase
just seed-local # local synthetic
just seed-local-fresh # local synthetic + --clear
just seed-local-copy # local prod copy
just seed-branch # branch synthetic (also pushes missing migrations, refreshes MVs)
just seed-branch --clear # branch synthetic + --clear
just seed-branch-copy # branch prod copy
# Branch lifecycle CLI
python3.12 scripts/supabase_branch.py ensure
python3.12 scripts/supabase_branch.py delete <git-branch>
python3.12 scripts/supabase_branch.py merge <git-branch>
python3.12 scripts/supabase_branch.py reap --dry-run
# Inspect current branch DB
set -a; . backend/.env.local; set +a
DB_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
psql "$DB_URL"
# After any migration
cd frontend && just gen-types # regen Database TypeScript types
git add frontend/shared/src/types/database.generated.ts
See also¶
- Supabase Preview Branches — branch lifecycle + troubleshooting reference.
- Supabase Seeding — seed data layout + how to add a new table.
- Supabase Setup — auth flow, RLS architecture,
before_user_createdhook. - Development Workflow — overall git + PR + merge process.
supabase/AGENTS.md— same content tuned for AI coding agents.