Branching and the Migrations Workflow
The idea in one sentence
Section titled “The idea in one sentence”A Supabase branch is a full preview copy of your project tied to a Git branch or pull request — its own isolated Postgres schema, and optionally its own seeded data — so you can test schema changes in isolation before they ever touch production, the same way a preview deployment isolates your app code.
What a Supabase branch actually is
Section titled “What a Supabase branch actually is”Without branching, “testing a schema change” usually means running it against a shared staging database, or worse, against production directly. A Supabase branch instead provisions a genuinely separate Postgres instance for the lifetime of a Git branch or pull request: its own schema, its own Data API, its own Auth and Storage, and — if you ask for it — a clone of your production data.
# Create a persistent preview branch (stays around across pushes, not tied to a single PR)supabase --experimental branches create --persistent
# Create a preview branch for a pull request, cloning production data into itsupabase --experimental branches create feature-add-comments --with-data
# List every preview branch on the linked projectsupabase --experimental branches listThis is not a feature flag toggled inside a single shared database — it is a fully separate database, which is exactly why it is safe to run destructive migrations, seed odd test data, or try a risky alter table against it without any risk to production.
Migrations: the file format that ties branches to production
Section titled “Migrations: the file format that ties branches to production”A branch only stays useful if the schema changes tested on it can be replayed reliably somewhere else. That is what migrations are for — plain SQL files, checked into version control, applied in order.
# Create a new, timestamped SQL migration file under supabase/migrations/supabase migration new add_comments_table
# Apply all pending local migrations to the linked project or branchsupabase db push
# Capture a schema change made through the Studio GUI as a migration file after the factsupabase db diff --schema publicsupabase migration new gives you an empty, timestamped .sql file to write your create table or alter table statements into by hand. supabase db diff covers the opposite case: someone made a change by clicking through the Studio GUI, and you want that change captured as a proper migration file instead of living only in the database. Either way, supabase db push applies every migration that has not yet run against whichever project or branch it is pointed at — the same command, whether the target is a preview branch or production.
Shipping migrations through CI, not by hand
Section titled “Shipping migrations through CI, not by hand”The value of branching mostly disappears if, after testing a migration on a branch, someone then opens a SQL editor and pastes it into production by hand. That step is exactly where typos, skipped files, and out-of-order migrations creep in. The fix is to make supabase db push part of the same CI pipeline that deploys your application code, so schema changes ship automatically and in order whenever a pull request merges.
# A representative CI step — runs after tests pass, before or alongside the app deploysupabase link --project-ref "$SUPABASE_PROJECT_REF"supabase db pushWith this in place, a migration file merged to the main branch is applied to production the same way every time, by the same automated process that already tested it against a preview branch — no human running ad hoc SQL against a live database, and no environment silently drifting out of sync with what is in version control.
flowchart LR
pr["Git branch / pull request"] --> branch["Supabase preview branch (isolated schema copy)"]
branch --> mig["Migrations applied and tested on the branch"]
mig --> merge["Pull request merged"]
merge --> ci["CI runs supabase db push"]
ci --> prod[("Production Postgres")]