Skip to content

Supabase branching vs. SupaClone: what branches miss

Published Jul 29, 2026 · 9 min read

Supabase branching does not copy your database. It replays the migration files in ./supabase/migrations against a fresh branch, and it copies no data at all. If your migration history is incomplete - and it is incomplete the moment anyone creates a table or edits an RLS policy in the dashboard - the branch silently comes up missing exactly the objects that were never written as migrations. SupaClone takes the opposite approach: it reads the live database and clones what is actually there, data included.

This post is about that specific gap. If you want the broader cost and lifecycle comparison, read Supabase branching vs. separate staging projects instead.

What does Supabase branching actually copy?

A branch is a new Postgres instance with its own API endpoints, Auth settings, Storage buckets, and Edge Functions. What lands inside that instance is decided by one rule, stated in the Supabase troubleshooting docs:

Branching in Supabase (Branching 2.0) relies on the current migration files in your project - not a schema dump - when creating environments from main.

The Branching 2.0 announcement spells out the branch point:

If your production branch has previous migrations (e.g., via CLI db push) then these will be run on the new branch. If your production branch has no previous migrations (e.g., all edits have been made through the Table Editor) then a db dump will be performed and run as a migration.

Read that carefully, because the failure mode hides in the word "previous". There are two paths, and only one of them looks at your real database:

  • Zero migrations anywhere: Supabase falls back to a db dump. You get the live schema. This works.
  • One or more migrations: Supabase replays the migration files and never dumps. You get whatever those files describe.

The dashboard branching docs confirm the same rule: "If you have run migrations on main, new branches will be created from existing migrations instead of a full schema dump."

Why does Supabase branching break with an incomplete migration history?

Because a partial history is treated exactly like a complete one. There is no third path.

The dump fallback only protects projects with a completely empty migration history. As soon as a single migration file exists, Supabase assumes the migration history is the truth and stops looking at the live database. Every table, policy, function, trigger, index, view, or enum that was created outside of a migration is invisible to the branching system.

That is the state most real projects are in:

  • Someone added a column in the Table Editor during a demo.
  • An RLS policy was hand-edited in the dashboard after a security review.
  • An index was created in the SQL Editor at 2am during an incident and never backported.
  • The project started in the dashboard, migrations were introduced six months later, and nobody ever ran supabase db pull to baseline the existing schema.

Supabase itself names the mechanism in the database migrations guide: using the dashboard SQL Editor or Table Editor on your remote database bypasses the migration history. Nothing warns you. The branch provisions successfully, reports green, and is missing objects.

The result is worse than a hard failure. A branch that is 90% correct passes a smoke test and then produces a wrong answer three days later, when someone queries the table that was never migrated.

Do Supabase branches contain production data?

No, and this one is by design rather than by accident. From the branching docs:

New branches do not start with any data from your main project. This is meant to better protect your sensitive production data.

The Branching 2.0 post puts it more bluntly: "A Supabase branch is essentially a copy of your Supabase project, minus the data."

Your options are a ./supabase/seed.sql file, with two caveats that matter in practice. First, the seed runs exactly once, at branch creation - to reseed you delete the branch and recreate it. Second, a seed file is synthetic data you have to write and maintain by hand. It will never reproduce the row that breaks your query, because you did not know about that row.

Storage has the same shape: buckets declared in config.toml get created, but the objects inside them do not come across. You get empty buckets.

What else is missing from a Supabase branch?

Two gaps are worth knowing about even if your migration history is perfect, because they are not fixable by writing better migrations.

Database privileges. Migrations capture DDL, not the grant setup a Supabase project receives at initialization. Issue #39877 documents the consequence: on preview branches, supabase_auth_admin lacks INSERT on auth.users, so admin.createUser() fails with "Database error creating new user". Auth flows that work in production break on the branch, and the suggested workaround (signUp() plus admin.updateUserById()) means testing a different code path than the one you ship.

Custom roles. Straight from the dashboard branching docs: "Custom roles created through the dashboard are not captured on branch creation." If your access model depends on custom Postgres roles, the branch does not have them.

Supabase branching vs. SupaClone: comparison

Supabase branchingSupaClone
Source of truthMigration files (db dump only if history is completely empty)The live database, always
Dashboard-made changesMissing, unless a migration exists for themIncluded - it reads what is actually there
Table dataNone. Seed file only, runs once at creationOptional per run, selectable
StorageBuckets from config.toml, no objectsBuckets, files, settings and policies, selectable
Edge FunctionsDeployed to the branchCode plus verify-JWT setting, selectable
Custom rolesNot captured on branch creationPart of the cloned schema state
TargetA child branch inside your production projectA fresh, empty project - any org, any account
VerificationDeployment status per stepField-by-field verification and a full report
LifecycleAuto-paused or deleted with the PRIndependent project you own
Cost model$0.01344 per branch per hour, Compute Credits do not applyOne-time or per-run, no hourly meter

When should you still use Supabase branching?

Branching is good software solving a different problem, and pretending otherwise would not help you pick correctly. Use it when:

  • You want per-PR preview environments. Open a PR, get an isolated stack with its own credentials wired into Vercel. Nothing else gives you that for cents per day.
  • You want migrations tested in CI. Every push replays new migrations against the branch. A migration that fails there is a migration that does not fail in production. This is genuinely the strongest argument for branching.
  • Your migration history is complete and enforced. If nobody touches the dashboard and every change lands as a file, the migration replay model is accurate, and branching is the cheapest way to use it.

Branching is a forward tool: it validates changes you are about to make. It was never designed to reproduce the current state of a live project.

When is a clone the right tool?

Use a real clone when you need the project as it exists right now, not as your migration files describe it:

  • Staging that has to match live production, drift included, so a bug you cannot reproduce locally reproduces there.
  • Realistic data, because the query plan, the null column, and the duplicate row only show up with real rows in the table.
  • A project in a different account or organization - handing a project to a client, splitting a codebase, separating billing. A branch always lives inside the parent project; see copying a Supabase project to another account.
  • A migration history you know is incomplete and that you are not going to reconstruct by hand this quarter.

SupaClone clones your own schemas - tables, RLS policies, functions, triggers, indexes, views, enums, extensions - and, optionally, their table data, into a fresh empty Supabase project. It uses native pg_dump/pg_restore with a baseline-aware plan that correctly skips the Supabase-managed schemas (auth, storage, realtime), so auth.users is never copied. Storage and Edge Functions are native, selectable options. Secrets are never copied; they appear as explicit manual steps in the report. Connection runs through Supabase OAuth, so there are no connection strings to paste around, and every run ends in field-by-field verification plus a report of what was cloned, skipped, failed, or needs a manual step. The plans start at $9.99 one-time for 1 full clone run - no subscription.

How do you check whether your migration history is complete?

Before you trust branching with anything that matters, run the check. With the CLI linked to your project:

supabase link --project-ref <your-project-ref>
supabase db diff --linked

Empty output means your local migrations describe the remote schema and branching will produce an accurate branch. Any output at all is drift: those are the objects your next branch will not have. You can baseline them with supabase db pull --linked, which generates a migration reflecting the current remote schema, then answer yes when the CLI asks to update the remote migration history table.

That baseline is worth doing regardless. It fixes branching going forward - though it does not retroactively give you data, storage objects, privileges, or custom roles. For the objects pg_dump refuses to hand over cleanly, our pg_dump and pg_restore guide covers the permission errors you will hit doing this by hand.

FAQ

Does Supabase branching copy my database schema?

Only indirectly. Branching replays the migration files in ./supabase/migrations against the new branch. Supabase performs a db dump of the live schema only when your project has no migrations at all. With a partial migration history, everything created outside of a migration is missing from the branch.

Why is my Supabase branch missing tables?

Almost always because those tables were created in the dashboard rather than through a migration. Since branching builds from migration files, objects with no corresponding migration do not exist on the branch. Run supabase db diff --linked to see the full list of what is missing.

Can I copy production data into a Supabase branch?

Not directly. Branches start with no data from the main project, deliberately, to protect sensitive production data. You can supply a supabase/seed.sql file, but it runs once at branch creation and contains only data you wrote yourself. To get real data into a separate environment you need a clone - see how to clone a Supabase project.

Is SupaClone a replacement for Supabase branching?

No, they solve different problems. Branching validates changes you are about to make, per PR, and is the right tool for CI migration testing. SupaClone reproduces the current state of a live project into a fresh project, with data, which branching does not attempt. Teams commonly use both.

Does SupaClone copy auth.users?

No. SupaClone skips the Supabase-managed schemas (auth, storage, realtime), so auth.users is never copied. Storage is handled separately as a native, selectable option that clones buckets, files, settings, and policies.

Clone your Supabase project without the pg_dump pitfalls

SupaClone copies schemas, tables, RLS policies, functions, triggers, and data into a fresh project - verified after every run. Pay once, no subscription.

Clone your first project