Skip to Content
Setting Deployment PipelineSupabase Branching

Supabase Branching Guide


What’s Branching?

Supabase Branching lets you spin up isolated copies of your database for dev/staging/testing. It’s basically Git branches but for your whole Supabase stack — database, auth, storage, edge functions, the works.

Each branch gets its own:

  • Postgres database
  • API URL and keys
  • Auth config
  • Storage buckets
  • Edge Functions
  • Realtime

Preview branches are temporary — they auto-pause after inactivity and get deleted when you merge or close the PR. Good for quick feature testing.

Persistent branches stick around until you delete them. Use these for staging or QA environments.


Setting It Up

Enable branching

  1. Go to the Supabase Dashboard
  2. Click your profile icon (top right)
  3. Select “Branching via dashboard”
  4. Hit “Enable feature”

Create a branch

  1. Look at the top bar — it shows ORG / PROJECT / BRANCH
  2. Click the arrows next to the branch name
  3. Click “Create branch”
  4. Name it something useful (staging, feature-new-auth, whatever)

Switch branches

Just use the dropdown in the top bar. Whatever you do (run SQL, edit tables, change config) happens on the branch you’ve selected.

Merge to production

  1. Click the merge request button next to the branch selector
  2. Review your changes
  3. Merge

Moving Data to Branches

Branches start empty. No production data. Here’s how to fix that.

Sometimes Supabase fails to automatically apply migrations when creating a branch — you’ll end up with an empty schema. When that happens, a full dump is the quickest fix.

Basic dump and restore

# Dump from production pg_dump -h db.<prod-ref>.supabase.co -U postgres -d postgres \ -n public \ --no-owner --no-privileges \ -f dump.sql # Restore to branch (use session mode for big dumps) psql -h aws-0-eu-central-1.pooler.supabase.com -p 5432 \ -U postgres.<branch-ref> -d postgres < dump.sql

With auth users (below only applicable for supabase auth)

If you need users too:

{ pg_dump -h db.<prod>.supabase.co -U postgres -d postgres \ --data-only -t auth.users -t auth.identities --no-owner --no-privileges pg_dump -h db.<prod>.supabase.co -U postgres -d postgres \ -n public -n audit --no-owner --no-privileges } > full_dump.sql

Don’t forget extensions

Before restoring, enable any extensions your schema needs:

CREATE EXTENSION IF NOT EXISTS citext WITH SCHEMA extensions; CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA extensions;

Otherwise you’ll get “type does not exist” errors and things will break.


Useful pg Commands

Connecting

# Through pooler (works on IPv4 networks, i.e. most home setups) psql -h aws-0-eu-central-1.pooler.supabase.com \ -U postgres.<project-ref> -d postgres # Direct connection (needs IPv6) psql -h db.<project-ref>.supabase.co -U postgres -d postgres # Session mode — use this for long operations like big restores psql -h aws-0-eu-central-1.pooler.supabase.com -p 5432 \ -U postgres.<project-ref> -d postgres

Dumping

# Single schema pg_dump -h <host> -U <user> -d postgres -n public -f dump.sql # Multiple schemas pg_dump -h <host> -U <user> -d postgres -n public -n audit -f dump.sql # Data only pg_dump -h <host> -U <user> -d postgres --data-only -f data.sql # Specific tables pg_dump -h <host> -U <user> -d postgres -t auth.users -t auth.identities -f tables.sql # Skip Supabase internal schemas pg_dump -h <host> -U <user> -d postgres \ -N auth -N storage -N extensions -N supabase_functions \ -N pgsodium -N vault -N realtime -N graphql \ -f dump.sql

Restoring

# Basic psql -h <host> -U <user> -d postgres < dump.sql # With progress bar (install pv first: brew install pv) pv dump.sql | psql -h <host> -U <user> -d postgres # Skip FK checks during restore psql -h <host> -U <user> -d postgres <<EOF SET session_replication_role = 'replica'; \i dump.sql SET session_replication_role = 'origin'; EOF

Poking around

-- Schemas \dn -- Tables \dt public.* -- Functions \df public.* -- Table structure \d table_name -- Extensions \dx -- Roles \du -- Table sizes SELECT table_name, pg_size_pretty(pg_total_relation_size(quote_ident(table_name))) AS size FROM information_schema.tables WHERE table_schema = 'public' ORDER BY pg_total_relation_size(quote_ident(table_name)) DESC; -- Row counts SELECT schemaname, relname, n_live_tup FROM pg_stat_user_tables ORDER BY n_live_tup DESC;

Gotchas

  • Custom roles made through the dashboard don’t carry over to branches
  • You can only merge to main, not between branches
  • Pulling updates from main overwrites your edge functions
  • Deleting functions has to be done on main manually
  • Migration conflicts need manual fixing
  • Auth/storage data doesn’t copy automatically

Tips

  1. Use persistent branches for staging — preview branches will pause/delete on you
  2. Always enable extensions before restoring
  3. Use port 5432 (session mode) for big restores — the default transaction mode times out
  4. Don’t dump system schemas (auth, storage, etc.) — just use -n public
  5. Use --data-only when dumping auth tables since the schema already exists
  6. Test migrations on a branch before touching production

Connecting to Your App

Each branch has its own credentials. Grab them from your branch’s dashboard under Settings → API and Settings → Database.

.env setup

# .env.local (or .env) # Supabase client NEXT_PUBLIC_SUPABASE_URL=https://<branch-ref>.supabase.co NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGci... # Server-side only SUPABASE_SERVICE_ROLE_KEY=eyJhbGci... # Database connection (for migrations, Prisma, Drizzle, etc.) DATABASE_URL=postgresql://postgres.<branch-ref>:[password]@aws-0-eu-central-1.pooler.supabase.com:6543/postgres

Swap out <branch-ref> and keys when switching branches.


Last updated on