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
- Go to the Supabase Dashboard
- Click your profile icon (top right)
- Select “Branching via dashboard”
- Hit “Enable feature”
Create a branch
- Look at the top bar — it shows
ORG / PROJECT / BRANCH - Click the arrows next to the branch name
- Click “Create branch”
- 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
- Click the merge request button next to the branch selector
- Review your changes
- 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.sqlWith 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.sqlDon’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 postgresDumping
# 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.sqlRestoring
# 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';
EOFPoking 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
- Use persistent branches for staging — preview branches will pause/delete on you
- Always enable extensions before restoring
- Use port 5432 (session mode) for big restores — the default transaction mode times out
- Don’t dump system schemas (
auth,storage, etc.) — just use-n public - Use
--data-onlywhen dumping auth tables since the schema already exists - 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/postgresSwap out <branch-ref> and keys when switching branches.