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:
| Artifact | Purpose | How you use it |
|---|---|---|
supabase/schemas/ | The desired current schema | Edit tables, functions, views, policies, and other definitions |
supabase/migrations/ | The ordered history of database changes | Review, 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.
| Command | Starting state | Target state | Reads declarative files under pg-delta? |
|---|---|---|---|
supabase db schema declarative sync | State represented by existing migrations | Definitions in supabase/schemas/ | Yes |
supabase db diff | Shadow database built from existing migrations | Live local database, or a specified remote database | No |
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:
syncsees the change. The schema files specify a column absent from migration history, so it can generate anADD COLUMN.db diffsees 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 diffcan detect the live-database change.synccannot; 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:
- Keep your current schema layout.
- Enable
pg-delta. - Remove the old
schema_pathsordering configuration. - Switch migration generation from
db difftodb schema declarative sync. - 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.