Migrations live in supabase/migrations/ and are tracked by the Supabase CLI. Each
database records what it has applied in supabase_migrations.schema_migrations, and supabase db push runs
only what is missing. The SQL from before the switch is gone from the tree. Older scripts and docs that cite
backend/supabase/migrations/ or migration.sql mean those files as of the pre-cli-migrations git tag.
npx supabase migration new add_my_field # creates supabase/migrations/<timestamp>_add_my_field.sql
yarn test:db:reset # rebuilds the local database from all migrations, plus the seed
yarn db:push:dev # optional: apply it to the shared dev database before merging
yarn --cwd=backend/api regen-types-dev # once dev has itMerging to main applies it: .github/workflows/cd-backend.yml pushes to dev, then to prod, and only then
deploys the API if its version changed. There is no approval step: merging is the decision to ship.
That workflow runs on every push to main, whether or not tests pass or the API version changed. Dev gets
db push every time, which does nothing when it is up to date. Prod runs only when the push changes
supabase/migrations/, so a migration whose prod run failed needs a re-run of that workflow. Prod is skipped
when dev fails. Nothing else checks a migration before it reaches prod, so review it before merging, and hold
back a slow or destructive one (a big index, a long lock, a data rewrite) until a quiet time.
- Never run
db push,db reset --remoteormigration repairfrom a laptop against prod. Those commands act on whatever the checkout is linked to, so keep checkouts linked to dev. - Never run
supabase config push, against dev or prod.supabase/config.tomlconfigures the local stack only, and that command would copy its local values, such as the127.0.0.1site URL, onto the linked project. - Don't edit a migration once merged. Write a new one. The same goes for one already pushed to dev with
yarn db:push:dev: dev records it as applied and will not run it again, so either write a follow-up migration or undo the change on dev by hand. - Rebase before merging if
maingained a newer migration:db pushrefuses a file older than the last one applied, and the timestamp comes from when the file was created. Rename it with a fresh timestamp. - Reference rows are not in the baseline.
db pullcopies the schema, not data, so a fresh database has nobanned_networksrows. Prod and dev keep theirs. New rows that every database needs belong in a migration.
The CLI has no down migrations. Undo a change with a new migration, add before you drop (ship the new shape, move the code, drop the old shape in a later release), and keep point-in-time recovery as the last resort.
Run by a human, in this order. Steps marked prod touch the production database.
Before you start. This checkout is linked to prod today, so link deliberately at each step. Hold new
migrations until this is finished. Check that dev and prod both run Postgres
17, as major_version in supabase/config.toml says, or the baseline may not rebuild locally.
-
Rehearse on dev. Clear the generated files first, or
db pullstops on a history mismatch.rm -f supabase/migrations/*.sql npx supabase link --project-ref zbspxezubpzxmuxciurg SUPABASE_DB_PASSWORD=ZTNlifGKofSKhu8c npx supabase db pull --remote baseline # answer n to "Update remote migration history table?" rm supabase/migrations/*_baseline.sql
-
Pull the baseline (prod). Answering
nkeeps this read-only.npx supabase link --project-ref ltzepxnhhnrnvovqblfr npx supabase db pull --remote baseline # answer nDelete the
CREATE TRIGGER … ON realtime.…and… ON storage.…statements at the end: they are Supabase's own, and they fail on a local stack without Realtime and Storage.Read the file, and check that every migration you expect on prod, as of the
pre-cli-migrationstag, is reflected in it. -
Prove it locally.
yarn test:db:resetwipes the local database, thenyarn test:integration. -
Align dev.
db difflists what dev has that the baseline does not.npx supabase link --project-ref zbspxezubpzxmuxciurg SUPABASE_DB_PASSWORD=ZTNlifGKofSKhu8c npx supabase db diff --remote
If it is empty, run
npx supabase migration repair --status applied <version> --remote. If not, rebuild dev with./scripts/dev_db_reset.sh, which wipes dev. -
Mark the baseline applied (prod). This writes one row to the history table and changes no schema.
npx supabase link --project-ref ltzepxnhhnrnvovqblfr npx supabase migration repair --status applied <version> --remote npx supabase migration list --remote # the baseline must show on both sides npx supabase link --project-ref zbspxezubpzxmuxciurg
-
Set up GitHub. Create the
devandprodenvironments, each with aSUPABASE_DB_PASSWORDsecret holding that database's password, and limit both to deployments frommain. -
Merge. Do not merge before steps 5 and 6: this merge adds a migration file, so it triggers the workflow. Without the history row
db pushwould run the baseline against prod. The first run should find nothing to apply.
CI connects with a connection string to the session pooler, aws-1-us-west-1.pooler.supabase.com:5432, so it needs
no Supabase access token, and the password must not contain characters that need percent-encoding. If a
project's pooler host changes (see Connect in its dashboard), update .github/workflows/cd-backend.yml.