Supabase migrations: native GitHub integration
Supabase’s GitHub integration deployssupabase/migrations/. There is no
migration workflow of our own.
Flow: PR into
staging (preview branch builds the schema from the files) ->
merge (applies to staging) -> release PR staging -> main -> merge (applies
to production).
Layout (since 2026-10-16)
supabase/migrations/20261016205500_prod_schema_baseline.sql: production’s schema, dumped 2026-10-16 20:55 UTC with thesupabase db dumprecipe. Every branch starts here.supabase/migrations/after the baseline: migrations not yet in production.supabase/migrations-archive/: every earlier file, original names. Supabase ignores this folder. Tests read both folders throughlib/migrations/__tests__/migration-files.ts, so guard tests still check every new migration.
Why a baseline
The old files could not rebuild the database. Tables (ai_token_usage_daily,
api_token_usage, import_agent_runs), functions and a role
(analytics_read) were created by hand and never committed, 78 dates had two
or more files sharing one version, and some files ran in the wrong order. Every
Supabase branch build failed, and the integration has reported
MIGRATIONS_FAILED on staging and main for months.
Left out of the baseline on purpose: Supabase-managed schemas, scratch schemas
(cfo_scratch, repair_ops), one-off backup/recon tables, event triggers and
pg_cron schedules (data, not schema). The baseline resets Supabase’s default
grants before restoring production’s, so branches get production’s privileges.
Rules for new migrations
- Unique 14-digit version.
npx supabase migration new <name>. CI runsscripts/check-migration-versions.mjsin the lint job. - No
CONCURRENTLY; wrapLOCK TABLEinBEGIN/COMMIT. Supabase’s runner sends a file as one batch. For an index on a big production table, build itCONCURRENTLYby hand first, then merge a plainCREATE INDEX IF NOT EXISTS. - Never change a database by hand. A recorded version with no file stops every later deploy, and the change is lost the next time a branch is built.
Cutover
- PR preview branch builds green from the baseline.
- Staging: back up
supabase_migrations.schema_migrations, then record the baseline and the files after it as applied (they are already on staging). - Merge the PR to
staging; the integration should report staging up to date. - Production (separately approved): back up and record the baseline as applied. The files after it are not in production yet; the next release applies them.
- Supabase dashboard: production branch
mainwith “Deploy to production”, staging persistent branch on gitstaging, automatic branching “Supabase changes only”. GitHub: makeSupabase Previewa required check.
Known drift, to fix later
- Staging is ahead of production. Migrations on
mainthat production never got (the multi-portfolio backend 20260928_06..20261008, the snaptrade webhook and vault_folders unique indexes, the ai_chat_logs FTS indexes) are in the archive but missing from production and the baseline. Staging has them. - Staging also differs from production on policies, grants and functions (staging: 445 RLS policies, production: 640).