Supabase’s branching feature lets you spin up a completely isolated, production-like environment for every pull request, so you can test changes without touching your live database.

Here’s a look at a Supabase project in action:

{
  "id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
  "name": "My Awesome App",
  "project_ref": "my-awesome-app-ref",
  "region": "us-east-1",
  "created_at": "2023-10-27T10:00:00Z",
  "db_version": "14.9.0",
  "framework": "nextjs",
  "git_branch": "main",
  "git_remote": "https://github.com/user/my-awesome-app.git",
  "preview_enabled": true,
  "preview_branches": [
    {
      "id": "pr-123",
      "name": "feature/new-login",
      "git_branch": "feature/new-login",
      "created_at": "2023-10-27T11:00:00Z",
      "url": "https://my-awesome-app-ref-pr-123.supabase.co",
      "status": "synced"
    },
    {
      "id": "pr-456",
      "name": "bugfix/auth-issue",
      "git_branch": "bugfix/auth-issue",
      "created_at": "2023-10-27T12:00:00Z",
      "url": "https://my-awesome-app-ref-pr-456.supabase.co",
      "status": "syncing"
    }
  ]
}

This JSON represents a Supabase project. The key here is preview_enabled: true. When this is set, Supabase can create separate database instances for each pull request targeting your primary branch (usually main). The preview_branches array shows these environments, each with a unique URL and status.

The Problem It Solves

Traditionally, testing database changes meant either:

  1. Testing directly on production: A recipe for disaster. A bad migration or data corruption wipes out your live application.
  2. Maintaining a separate staging environment: This is better, but it’s a single point of truth. Multiple developers working on different features can’t test their database changes simultaneously without interfering with each other or the staging environment itself. If two PRs require conflicting schema changes, one has to wait.

Supabase branching solves this by making each PR its own mini-production. When you open a PR against main, Supabase automatically:

  • Creates a new PostgreSQL database instance: This instance is a complete clone of your main branch’s database at that moment.
  • Applies the schema changes from your PR: It takes the SQL migrations defined in your PR and applies them to this new database.
  • Provides a unique API endpoint: You get a new supabase.co URL that points to this isolated database. Your application code can be pointed at this URL to test the changes in isolation.

How It Works Internally

At its core, Supabase branching leverages PostgreSQL’s snapshotting and cloning capabilities, combined with Git integration.

  1. Git Integration: You connect your Supabase project to a Git repository (e.g., GitHub, GitLab). Your Supabase schema is managed as SQL migration files within this repository.
  2. PR Detection: When a PR is opened targeting your primary branch, Supabase’s backend detects this event.
  3. Database Cloning: It initiates a process to clone your current production database. This isn’t a simple pg_dump and pg_restore; it’s often a more efficient, block-level cloning mechanism (depending on the underlying cloud provider) to create a near-instantaneous copy.
  4. Migration Application: The SQL migration files from the PR are then applied to this newly cloned database. If your PR includes migrations/0001_create_users_table.sql and migrations/0002_add_email_to_users.sql, these are executed sequentially on the cloned DB.
  5. API Endpoint Provisioning: A new API endpoint is provisioned, configured to point to this cloned and migrated database. This endpoint uses the same project ref as your main project but with a unique identifier for the PR.
  6. Environment Variables: You’ll typically use environment variables in your application to switch between the production API endpoint and the PR-specific endpoint. For example, in a Next.js app, you might have NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY. For a PR, you’d temporarily override these to point to the preview URL.

Levers You Control

  • Branching Configuration: In your Supabase project settings, you enable branching and specify which Git branch is your "main" branch (e.g., main, master).
  • Migration Files: All schema changes must be managed as SQL migration files within your Git repository. Supabase automatically detects these.
  • Environment Variable Management: Your application’s deployment process needs to dynamically set the Supabase URL and Anon Key based on whether you’re deploying the main branch or a PR. This is usually handled by your CI/CD pipeline. For local development, you might set a specific environment variable like IS_PR_PREVIEW=true and then conditionally set the Supabase URL.

Consider a scenario where you’re refactoring a products table and adding a new categories table, with a PR that includes these two migration files:

migrations/20231027100000_refactor_products.sql:

ALTER TABLE products DROP COLUMN old_price;
ALTER TABLE products ADD COLUMN current_price numeric(10, 2);

migrations/20231027100500_add_categories.sql:

CREATE TABLE categories (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) UNIQUE NOT NULL
);
ALTER TABLE products ADD COLUMN category_id INTEGER REFERENCES categories(id);

When this PR is opened, Supabase takes your production main database, clones it, applies 20231027100000_refactor_products.sql, and then applies 20231027100500_add_categories.sql. The resulting database instance, accessible via a unique *.supabase.co URL, will have the refactored products table and the new categories table.

The actual database cloning and migration application process is handled by Supabase’s infrastructure, abstracting away the complexities of PostgreSQL replication and migration management. You just need to ensure your migrations are in Git and your application can switch endpoints.

When a PR is merged into main, the corresponding preview environment is automatically torn down, saving resources.

The next challenge you’ll face is handling complex data seeding for your preview environments, especially when dealing with relational data that requires specific initial states for testing.

Want structured learning?

Take the full Supabase course →