Sign in

Published

Supabase Declarative Schemas v2: What Changes, and How to Ship With It

Supabase Declarative Schemas v2 changes what you edit, not what you deploy. You maintain the desired database structure in SQL files, and the new pg-delta engine generates incremental migrations from those definitions. You still review migrations, test them locally, and apply them to staging and production.

If you already maintain a directory of numerically ordered schema files, you don’t need to rebuild it. The biggest changes are a new migration-generation command, automatic dependency ordering, and a clearer separation between schema definitions and deployment history.

The Core Model: Desired State In, Migrations Out

Declarative schemas give you two complementary representations of your database:

ArtifactPurposeHow you use it
supabase/schemas/The desired current schemaEdit tables, functions, views, policies, and other definitions
supabase/migrations/The ordered history of database changesReview, commit, test, and deploy incremental SQL

Instead of manually writing an ALTER TABLE for every structural change, you edit the table’s final definition:

-- supabase/schemas/public/tables/employees.sql
create table public.employees (
  id bigint primary key,
  name text,
  age smallint
);

Then generate the migration needed to move from the state represented by your existing migrations to that definition.

Schema files are your editing surface. Migration files are your deployment artifact.

The schema files themselves are not applied directly to production. Deployment still uses supabase db push to apply pending migrations.

What’s New in v2?

A New Diff Engine

pg-delta replaces the legacy migra engine for this workflow. It supports a broader range of PostgreSQL objects, including RLS policies, grants, comments, domains, partitions, and publications.

That broader coverage matters if your schema includes more than tables and functions—but it is not complete coverage. Some object types and objects in Supabase-managed schemas remain outside the engine’s scope. Review the current declarative-schema documentation before assuming every database object will round-trip through this workflow.

Enable the engine in supabase/config.toml:

[experimental.pgdelta]
enabled = true

A Dedicated Migration-Generation Command

The v2 command is:

supabase db schema declarative sync -f my_feature --no-apply

It reads your declarative files and generates the incremental migration. With --no-apply, you can inspect the SQL before applying it locally. Without that flag, sync can offer to apply the generated migration locally.

Do not substitute supabase db diff when you intend to generate migrations from schema-file edits. Under pg-delta, those commands compare different inputs.

Dependency Ordering Moves Into the Engine

Previously, you might have named files so that tables loaded before dependent views or RPC functions. With v2, pg-delta determines dependency order when generating the migration.

You can therefore organize schema files for readability rather than treat filenames as an execution plan. The old [db.migrations].schema_paths ordering setting is no longer needed for this workflow.

sync Versus db diff: The Distinction That Matters

Both commands can produce SQL migrations. The difference is which states they compare.

CommandStarting stateTarget stateReads declarative files under pg-delta?
supabase db schema declarative syncState represented by existing migrationsDefinitions in supabase/schemas/Yes
supabase db diffShadow database built from existing migrationsLive local database, or a specified remote databaseNo

Suppose your committed migrations create employees with only id and name.

You add age smallint to the declarative file, but you have not changed the running local database:

  • sync sees the change. The schema files specify a column absent from migration history, so it can generate an ADD COLUMN.
  • db diff sees no change. The running database and the migration-built shadow database still contain the same two columns.

Now reverse the situation: add age directly in local Studio without editing the schema file.

  • db diff can detect the live-database change.
  • sync cannot; it does not inspect the running database.

Edited SQL definition files → sync. Edited a running database → db diff.

In a declarative v2 project, the first path should be your normal workflow. Older tutorials that say “edit supabase/schemas/, then run db diff” describe the legacy behavior, not the pg-delta workflow.

What Happens to Your Numerically Prefixed Files?

You Can Keep Them

If your existing schema files already describe the intended database, you do not need to regenerate them to adopt v2.

A file such as:

supabase/schemas/1001-rpcs.sql

can stay exactly where it is. If the 1001 prefix exists only to ensure functions are processed after their dependencies, you can later rename it to rpcs.sql or split it into individual files.

For example, an orders table may reference customers. Previously, you had to ensure the customers definition was processed first. Now the engine orders the generated migration statements appropriately. The same principle applies to views over tables and functions used by triggers.

This reduces configuration and ordering-related failures during file reorganizations. It does not mean SQL execution order stops mattering: the engine handles that order in the migration, which you still need to review. An unresolvable dependency cycle results in an error rather than a usable migration.

Migration Filenames Are Different

Do not apply the same cleanup to:

supabase/migrations/1001-rpcs.sql

Migrations remain ordered deployment history. V2 does not turn existing migrations into freely reorganizable schema files.

A sensible adoption sequence is:

  1. Keep your current schema layout.
  2. Enable pg-delta.
  3. Remove the old schema_paths ordering configuration.
  4. Switch migration generation from db diff to db schema declarative sync.
  5. Treat filename cleanup as a separate refactor.

Generating or Refreshing Schema Files

There are two distinct operations: exporting definitions and generating migrations.

Export an Existing Database Into Per-Object Files

To create declarative files from your linked database:

supabase db schema declarative generate --linked

The export produces multiple, per-object SQL files, organized by database schema—not one giant SQL file. A table might appear at:

supabase/schemas/public/tables/employees.sql

This command exports schema definitions. It does not create a migration.

You can use it to adopt Supabase’s generated layout, but v2 does not require that layout. If you already have a useful hand-maintained tree, keep it initially.

Refresh an Existing Declarative Tree

To refresh existing definitions from the linked database, Supabase recommends:

supabase db pull --declarative
git diff -- supabase/schemas/

This replaces the schema files without creating a migration or changing migration history. You can also rerun generate --linked; --overwrite replaces an existing tree without prompting.

Before either operation:

  • Verify which project is linked.
  • Preserve uncommitted schema edits.
  • Review the resulting Git diff.
  • Check for intentional definitions or unsupported objects that an export might not preserve.

Refreshing definitions does not repair migration history. If the database has changed outside the normal workflow, you must also reconcile that history before the next sync.

For an existing project with no migrations, establish a baseline with supabase db pull before your first sync. Otherwise, the engine may generate SQL that tries to recreate objects already present in the deployed database.

A Practical Local → Staging → QA Workflow

1. Start From the Committed Team State

Pull the latest repository changes and bring your local environment into line with committed migrations.

supabase start
supabase db reset

db reset recreates the local database and discards local data. Use it deliberately, with reproducible seed data where appropriate.

2. Edit the Desired Schema

Change the relevant files in supabase/schemas/: table definitions, RPC functions, policies, grants, or other supported objects.

Do not make a Studio-only change and expect sync to discover it. Your schema files must contain the intended change.

3. Generate and Review the Migration

supabase db schema declarative sync -f my_feature --no-apply

Inspect every generated file under supabase/migrations/. A change normally produces a migration file, though some changes can require multiple ordered files.

Pay particular attention to:

  • Unexpected drops or recreations.
  • Renames that could become destructive operations.
  • Permission and RLS changes.
  • Changes that might lose existing data.

Fix the definitions or migration as appropriate before deployment.

4. Test Incremental Application and Full Replay

First, apply pending migrations to your current local database:

supabase migration up

Run the application and feature tests. Then check that the entire migration history can recreate the database:

supabase db reset

Run the tests again. These checks serve different purposes: incremental application tests the change against your current local state; reset validates a fresh replay of the full chain.

If you use generated client types, regenerate them from the updated local database.

5. Commit Both Representations

Commit together:

  • Changed declarative schema files.
  • Every generated migration file.
  • Corresponding application changes.
  • Updated generated types, if applicable.

The definitions explain the intended end state. The migrations tell the next environment how to reach it.

6. Deploy the Reviewed Migrations to Staging

Prefer a CI job tied explicitly to your staging project. For a manual deployment:

supabase link --project-ref <STAGING_PROJECT_REF>
supabase db push
supabase migration list

Verify the project reference before pushing. Environment-specific CI configuration and credentials reduce the risk of deploying to whichever project your working directory happens to be linked to.

db push applies pending migrations; it does not generate fresh migrations from schemas/. Deploy the corresponding application build, then hand the staging environment to QA.

7. Make QA Fixes as New Migrations

When QA finds an issue, repeat the cycle: edit definitions, run sync, review, test locally, and deploy another migration.

Do not rewrite a migration that staging has already applied. After approval, promote the same tested migrations and application changes to production rather than generating a new production diff.

The Benefits—and the Boundary

The main benefit is readability. You can find a function’s current definition without reconstructing it from successive migrations. Code review shows the desired state, while migration review shows the deployment operation. Coding agents also get a clearer task: edit the SQL definition rather than invent the migration steps.

But declarative schemas are not production autopilot. Schema diffs do not capture INSERT, UPDATE, or DELETE; use seed files or hand-written migrations for data changes. Generated structural changes still require operational review.

The workflow to remember is:

Schema files → reviewed migrations → local testing → staging → QA → production.

V2 makes the first step easier and dependency management less fragile. It leaves the discipline of repeatable, reviewed deployment intact.