Appearance
Database Migrations
Migrations are the only way the schema changes. Live DB edits (Studio) are not a source of truth.
Rules [ENFORCED]
- Filename: 14-digit
YYYYMMDDHHMMSS_name.sql. Prefernpx supabase migration new {name}. Never the oldNNN_form — it collides on the version parser. - Never edit a merged migration — add a new one.
- Expand-contract, always. Never a breaking change in one step.
- RLS on every user-facing table. Append-only tables carry no UPDATE/DELETE policy.
- Use
row_to_json(table.*)in RPCs for forward-compat. - Capture
pg_cronschedules and storage buckets as migrations, not just live state.
Expand-contract
Each arrow is (potentially) a separate migration + deploy. The app is always runnable at every step — old and new code both work against the intermediate schema.
The baseline
The core product schema is captured once as a schema-only squash: supabase/migrations/20260710134136_baseline_schema_from_prod.sql (supersedes 3 archived pre-baseline files — see _archive_pre_baseline/README.md). Verified 58/58 tables and 118/118 RLS policies identical between Dev and Prod, zero data rows on Dev.
Workflow
bash
npx supabase migration new add_thing_flag # creates the timestamped file
# ...write expand-contract SQL + RLS...
# apply to Dev, verify, then Prod (see Promotion)Promotion (mandatory)
Every migration is promoted to both Supabase projects deliberately (Dev first, then Prod). Dev/UAT does not self-sync to Prod. Full procedure + parity-verification SQL in supabase/docs/PROMOTION_RUNBOOK.md. See Deployment & Promotion.
[TRANSITIONAL] — not yet on Dev
- pg_cron job — hardcodes Prod's Edge Function URL; needs a project-URL-aware rewrite first.
profile-picturesstorage bucket — environment-specific data, not portable schema.
Rollback (DR)
Backend rollback = a compensating expand-contract migration (not an in-place revert). Supabase managed daily backups are the interim safety net; PITR + formal RPO/RTO are a deferred decision.