Files
Andrew Valleteau e357ec8f9f docs(cli): update local development workflow docs for pg-delta default diffing (#49280)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Docs update.

## What is the current behavior?

Linear:
[CLI-1618](https://linear.app/supabase/issue/CLI-1618/update-cli-workflow-docs-for-pg-delta-default-diffing)

Four docs pages lag the shipped CLI behavior now that `pg-delta` is the
default diff engine for projects created by a recent `supabase init`:

- **CLI workflows** claims `db diff` compares `supabase/schemas/`
against migrations. Under `pg-delta`, declarative files are never the
`db diff` baseline (and `[db.migrations].schema_paths` no longer changes
it) — the declarative flow goes through `supabase db schema declarative
sync`. The cleanup guidance describes `migra`-era output.
- **Declarative database schemas** teaches the old `db diff -f` +
`schema_paths` flow throughout, and its known-caveats list is the
`migra` issue list.
- **Managing environments** still presents `--use-migra` as an
"experimental flag" for a "more concise" diff — inverted now.
- **Backup and restore (migrating within Supabase)** and the CLI
workflows guide both steer users to `db diff`/`db pull` with `--schema
auth,storage`. Under `pg-delta`, `--schema` layers an extra exclude
policy on top of the Supabase profile: it can only narrow a diff, never
re-include managed schemas, and managed-schema selections can even fail
closed (e.g. `--schema auth` when a trigger function lives in `public`).
Unfiltered diffs are the supported path.

## What is the new behavior?

All claims verified against the CLI source at current `develop` —
including supabase/cli#6300, which upgraded the engine to
`@supabase/pg-delta` 1.0.0-alpha.46 — against the pinned pg-delta
package source (profile rules, format defaults, coverage doc), and
against a live dogfood run of the documented workflows on `develop`
`38f31b4` (two OSS corpus projects, warm shadow cache).

- **`cli-workflows.mdx`**: adds a "Which diff engine you're on" note
(`pg-delta` for new `supabase init` projects, `migra` for existing ones
until they opt in by adding `[experimental.pgdelta] enabled = true`;
per-run fallbacks `--use-migra` on `db diff` / `--diff-engine migra` on
`db pull`); corrects `db pull` and `db diff` mechanics (shadow built
from migrations vs. live database; the baseline history record is
offered, not unconditional); switches the declarative flow to `supabase
db schema declarative sync`; reworks the cleanup section around pg-delta
output (uppercase keywords at max width 180, `format_options`, per-unit
migration files with numeric segment suffixes, the `-- pg-delta:
transaction=false` directive on genuinely non-transactional files,
engine-neutral grant/revoke review guidance, coverage warnings +
`--strict-coverage`); documents what pg-delta captures in managed
schemas (user triggers, RLS policies on `auth` tables and on
`storage.objects`/`storage.buckets`/`realtime.messages`) versus what it
doesn't; adds key-command rows for the declarative commands and
troubleshooting entries (`db pull` non-zero exit when in sync, the
`schema_paths` warning, `PGDELTA_DEBUG=1` bundles under
`supabase/.temp/pgdelta/v2/debug/`).
- **`declarative-database-schemas.mdx`**: swaps `db diff -f` for `db
schema declarative sync -f` throughout; replaces
lexicographic/`schema_paths` ordering guidance with automatic dependency
ordering and the `generate` export layout (`_cluster/`, reserved
`_custom/`); bootstraps from production via `db schema declarative
generate --linked` (explicit target + `--overwrite` in scripts) and
refreshes via `db pull --declarative`; rewrites known caveats for
pg-delta (DML including storage buckets, untracked object kinds + the
`_custom/` escape hatch, managed schemas, extension-managed objects, and
the two gates when adopting an existing schema tree:
`[experimental.webhooks]` for `pg_net` migrations and declaring the
tree's extensions) keeping the `migra` workflow and issue list under a
legacy section for projects that haven't enabled it.
- **`managing-environments.mdx`**: frames the verbose grant sample as
legacy-engine output, notes that generated migrations can include grant
statements on any engine, describes `--use-migra` as a single-run
fallback, and adds a `db diff --strict-coverage` CI step.
- **`backup-restore.mdx`**: replaces `db diff --linked --schema
auth,storage` with a plain `db diff --linked` on `pg-delta` (keeping the
`--schema auth,storage` form for the legacy engine) and explains what
the engine includes (user triggers on managed tables, user RLS policies
on `auth`, `storage.objects`/`storage.buckets`/`realtime.messages`) and
what must be recreated manually.
- **New `diff-engines.mdx` page** (from #49889): the single home for how
the engine is selected, a behavior matrix for `pg-delta` versus `migra`,
the per-command fallback flags, a procedure for switching an existing
project (the first `db pull` after enabling may write a catch-up
migration), and how to go back with `enabled = false`. Registered in
navigation. A shared `diff_engine_check` partial replaces the inline
engine parentheticals across seven pages, and a
`managed_schemas_diff_capture` partial carries the managed-schema
capture rules.
- **CLI reference (`cli_v1_commands.yaml`, `cli_v1_config.yaml`)**: `db
pull`, `db schema declarative sync`/`generate` flags and descriptions,
`experimental.pgdelta.*` and `db.migrations.schema_paths` config keys,
and the `db diff` description updated to describe both engines. Note
that `cli_v1_commands.yaml` is generated from the CLI repo;
[supabase/cli#6557](https://github.com/supabase/cli/pull/6557) carries
the matching `db pull` example and overlay text so the next publish
keeps it.
- **`examples/prompts/declarative-database-schema.md`**: rewritten for
the `db schema declarative sync` flow, with the `[experimental.pgdelta]`
prerequisite.

## Additional context

The first draft was written against pg-delta 1.0.0-alpha.42.
supabase/cli#6300 (engine upgrade to alpha.46) then changed two
documented behaviors, both reflected here: generated SQL now defaults to
uppercase pretty-printed keywords, and user RLS policies on
`storage.objects`/`storage.buckets`/`realtime.messages` are included via
the engine's `SUPABASE_USER_POLICY_SURFACES` allowlist. A follow-up
dogfood run on `develop` `38f31b4` then falsified three more claims
(pg-delta emits no grant noise, `_schema_changes`/`_after_enum_values`
multi-file names, directive on every split file), all corrected in the
last commit.

**Update (Sep 14 to 17):**
[#49889](https://github.com/supabase/supabase/pull/49889) and
[#50220](https://github.com/supabase/supabase/pull/50220) were merged
into this branch, so this PR now carries the full stack. #50220
corrected the `schema_paths` warning wording (the CLI warns only when
the setting lists paths), added `auth` RLS policies to the
managed-schema partial, and described the migra initial pull accurately
(the `pg_dump` skips managed schemas and the migra diff pass that
follows appends the trigger and policy changes). It also reframed
`pg-delta` as the default for every project ahead of supabase/cli#6391.
That plan changed: no breaking default flip before Select, so
[#50332](https://github.com/supabase/supabase/pull/50332) restores the
opt-in framing (`pg-delta` requires `[experimental.pgdelta] enabled =
true`, which `supabase init` writes for new projects) and also resolves
the four CodeRabbit findings from the latest review round.

Two claims are pending confirmation from the owning teams: that
branching runs every migration in a transaction and ignores the `--
pg-delta: transaction=false` directive, and the `--db-url`
pooler-versus-direct connection advice, which currently disagrees with
the CLI's own `db pull` docs.

Stale spots found in the CLI repo's own docs while verifying (out of
scope here, worth follow-ups): four `SIDE_EFFECTS.md` files still claim
lowercase output, `docs/supabase/db/diff.md` still lists `migra`-era
"known failure cases" that alpha.46 fully models, the `supabase init`
template's commented `format_options` example shows `maxWidth: 80`
against an actual default of 180, and the CLI upgrade recipe appends
`--experimental` even when the config already enables pg-delta.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01SUuaVmXLRbV6tZjzhka3cp

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Clarified `pg-delta` and legacy `migra` behavior, configuration, and
switching guidance.
* Expanded declarative schema workflows, including synchronization,
migration generation, baselines, deployment, and legacy-engine support.
* Documented managed schemas, permissions, extensions, transaction
handling, dependency ordering, and troubleshooting.
* Added guidance for strict coverage checks, output directories,
non-interactive workflows, and declarative pull modes.
* Added a dedicated diff engines guide and updated CLI navigation,
backup and restore, branching, deployment, and CI documentation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Wen Bo Xie <wenbox323@gmail.com>
2026-09-21 12:07:10 +02:00

518 lines
30 KiB
Plaintext

---
id: 'cli-workflows'
title: 'Local development workflow'
description: 'Set up and run your day-to-day local development workflow with the Supabase CLI.'
subtitle: 'Set up and run your day-to-day local development workflow with the Supabase CLI.'
---
This guide walks through two common starting points for local development with the Supabase CLI, and shows how they converge into the same daily workflow. By the end, you'll have a `./supabase` directory in your repo that anyone can clone to recreate the full project, locally or on a fresh remote instance.
There are two starting points, both leading to the same place: database schema and migrations tracked in version control, with seed data for local development.
- **[Move an existing project to local development](#move-an-existing-project-to-local-development)**: you have a project on the Supabase platform and want to bring it into a proper local development workflow.
- **[Start a new project from scratch](#start-a-new-project-from-scratch)**: you're building locally and will eventually push to a remote instance.
## Before you begin
You need the Supabase CLI installed and a Docker-compatible runtime running. If you haven't set these up yet, see [Install and run the CLI](/docs/guides/local-development/cli/getting-started) for installation across macOS, Windows, and Linux, and for the details of what `supabase start` brings up and how to access each service.
Keep in mind that **the local stack is for development only**. It is not hardened for production use and must never be exposed to external traffic. It has no TLS, no rate limiting, and default credentials. Use it to develop and test, then deploy to the [Supabase Platform](https://supabase.com) or a proper self-hosted setup for anything beyond that.
<Admonition type="note" title="`supabase` vs. `npx supabase`">
How you invoke the CLI depends on how you installed it:
- Installed globally with **Homebrew or Scoop**: run `supabase <command>`.
- Added as a **project dependency** with npm, pnpm, yarn, or bun: run it through your package runner instead, for example `npx supabase <command>` (or `pnpm supabase`, `yarn supabase`, `bunx supabase`).
Every example in this guide is written as `supabase <command>`. Translate it to whichever form matches your install. See [Install and run the CLI](/docs/guides/local-development/cli/getting-started) for the full setup.
</Admonition>
<Admonition type="note" title="Starting from a template">
If you want a working project to explore rather than an empty one, `supabase bootstrap` scaffolds a starter application (Next.js, Flutter, and more) with schema, migrations, and config already wired up. It's an alternative entry point to `supabase init` when starting a new project from scratch.
</Admonition>
## The `./supabase` directory [#the-supabase-directory]
After `supabase init`, your project contains a `./supabase` directory. Here's what goes in it and what to commit:
| Path | Purpose | Commit? |
| ---------------------- | ----------------------------------------------------------------- | ------- |
| `config.toml` | Local stack configuration (ports, auth settings, etc.) | Yes |
| `migrations/` | Timestamped SQL migration files, applied in order | Yes |
| `seed.sql` | Dev/test data, applied after migrations on `start` and `db reset` | Yes |
| `schemas/` | Declarative schema files (if using that approach) | Yes |
| `.temp/`, `.branches/` | CLI internal state | No |
The `config.toml` is safe to commit. It contains no secrets by default. If you add sensitive values (OAuth credentials, API keys), use the `env()` function to reference environment variables instead of hardcoding them. See [Managing config and secrets](/docs/guides/local-development/managing-config).
<Admonition type="note" title="Local vs. remote targets">
Many database commands accept `--local` and `--linked` flags to choose what they act on. The defaults are not the same across commands: `db diff` and `db reset` default to `--local`, while `db pull`, `db push`, and `db dump` default to `--linked`. When in doubt, pass the flag explicitly.
</Admonition>
## Which diff engine you're on [#pg-delta]
`db diff`, `db pull`, and the `db schema declarative` commands generate SQL with a diff engine. The CLI ships two. [`pg-delta`](https://github.com/supabase/pg-toolbelt/tree/main/packages/pg-delta) is the engine for projects created with a recent `supabase init`, and [`migra`](https://github.com/djrobstep/migra) is the legacy engine.
<$Partial path="diff_engine_check.mdx" />
This guide describes `pg-delta` behavior and calls out where `migra` behaves differently. The `db schema declarative` commands require `pg-delta` and won't run on `migra`. For a side-by-side comparison and a switching procedure, see [Diff engines](/docs/guides/local-development/diff-engines).
## Move an existing project to local development
You've built a project on the Supabase platform, with tables created via the Dashboard, SQL editor, or client libraries. Now you want a local dev setup with everything in version control.
### Step 1: Initialize
In your project root:
```bash
supabase init
```
This creates `./supabase/config.toml`. If you already have a project directory with application code, run this at the root. The `supabase/` directory will sit alongside your app code.
### Step 2: Authenticate
```bash
supabase login
```
Opens a browser to generate an access token. The token is stored locally and used for all subsequent CLI commands that interact with the platform.
### Step 3: Link to your remote project
```bash
supabase link --project-ref <project-id>
```
Find your project ID in the Supabase Dashboard URL: `https://supabase.com/dashboard/project/<project-id>`.
This tells the CLI which remote project to connect to for `db pull`, `db push`, and other remote operations. You'll be prompted for the database password, which is the password set when you created the project.
### Step 4: Pull the remote schema
```bash
supabase db pull
```
This builds a shadow database from your local `supabase/migrations` directory (empty at this point), diffs your remote database against it, and saves the difference as a migration file:
```
supabase/migrations/<timestamp>_remote_schema.sql
```
On this initial pull, the whole migration comes from that diff, which captures your remote schema as executable SQL. This migration is your baseline. All future changes build on top of it. `db pull` also offers to record this migration as already applied in the remote migration history (the `supabase_migrations.schema_migrations` table). Accept it (non-interactive runs accept automatically) so a later `db push` won't try to reapply it.
If a change crosses a transaction boundary, `db pull` may write more than one ordered migration file instead of a single file. Commit all of them. On the legacy `migra` engine, this initial pull seeds the migration with `pg_dump` and appends a diff of what the dump skips.
When connecting with `--db-url` for `db pull` or `db diff`, prefer the direct connection (`db.<project-ref>.supabase.co:5432`) so `pg-delta` can read the full catalog. Direct connections require IPv6 or the IPv4 add-on, so on an IPv4-only network use the session pooler instead. Don't use the transaction pooler for these commands. For `db dump` and `psql`, the session pooler is the safe default.
<Admonition type="caution" title="Review the generated migration">
The generated file can include statements you didn't expect. The engine excludes Supabase platform-managed schemas, roles, and a small set of platform extensions, but extensions you enable yourself (`pg_net`, `pg_cron`, `pgcrypto`, and others) are diffed like any other object. A `DROP EXTENSION "pg_net";` appears when your local configuration or migrations enable the extension and the remote project doesn't have it. These statements apply silently on `db reset` and change your local schema, so read the file before committing it. See [Cleaning up generated migrations](#cleaning-up-generated-migrations) for what to look for.
</Admonition>
<Admonition type="note" title="Customizations in the `auth` and `storage` schemas">
<$Partial path="managed_schemas_diff_capture.mdx" />
Manage those objects with [hand-written migrations](/docs/guides/deployment/database-migrations). On `pg-delta`, omit `--schema` on later pulls. The flag only narrows the diff further and can't add managed objects back, so listing `auth` alone would drop a trigger whose function lives in `public`. On the legacy `migra` engine, the initial pull's `pg_dump` skips these schemas, but the diff pass that follows appends your triggers and RLS policies in them to the same migration. If an older CLI prints a message that `auth` and `storage` were excluded, run `supabase db pull --schema auth,storage` once.
</Admonition>
### Step 5: Create seed data
You have two options:
**Option A: Dump existing data from remote** (then clean it up):
```bash
supabase db dump --data-only --linked > supabase/seed.sql
```
<Admonition type="caution">
Review and clean up the dump before committing. Remove production user data, secrets, personal information, and anything sensitive. Keep only representative test data that a developer needs to work with the project.
</Admonition>
**Option B: Write seed data by hand** (recommended for most projects):
Create `supabase/seed.sql` with INSERT statements that set up a useful local development state, such as a few test users and sample data. This is often better than dumping production data because you control exactly what's in it.
For more on organizing seed files, glob patterns, and generating realistic data, see [Seeding your database](/docs/guides/local-development/seeding-your-database).
### Step 6: Verify
```bash
supabase start
supabase db reset
```
`db reset` destroys the local database and recreates it from scratch: it applies all migrations in order, then runs `seed.sql`. If this succeeds, your setup is reproducible. Anyone who clones the repo can do the same.
### Step 7: Commit
```bash
git add supabase/
git commit -m "add supabase local development setup"
```
Your project now has a fully reproducible local development environment.
<Admonition type="note" title="What about declarative schemas?">
For an existing project, the pulled migration is already your schema baseline. You don't need to also create a `schemas/` directory, because that would mean maintaining two representations of the same schema. If you want to adopt declarative schemas later, `supabase db schema declarative generate` bootstraps the `supabase/schemas/` directory (or the directory set by `experimental.pgdelta.declarative_schema_path`) from your live database. See [Declarative database schemas](/docs/guides/local-development/declarative-database-schemas). For day-to-day changes, see [The daily workflow](#the-daily-workflow).
</Admonition>
## Start a new project from scratch
No remote project yet. You're building from scratch and want to do it right from the start.
### Step 1: Initialize
```bash
supabase init
```
### Step 2: Start the local stack
```bash
supabase start
```
On first run, Docker images are pulled, which takes a few minutes. Subsequent starts are fast. Once running, the CLI outputs local service URLs and credentials, including the Studio URL for a local instance of the Dashboard. See [Install and run the CLI](/docs/guides/local-development/cli/getting-started#access-your-projects-services) for the full output and how to reach each service.
### Step 3: Create your schema
Two approaches, pick one:
**Option A: Declarative schema** (recommended for new projects)
Declare the state you want your database to be in as a file in `supabase/schemas/`, for example:
```sql title="supabase/schemas/schema.sql"
create table public.todos (
id bigint generated by default as identity primary key,
created_at timestamptz default now() not null,
title text not null,
is_complete boolean default false not null,
user_id uuid references auth.users (id) default auth.uid() not null
);
alter table public.todos enable row level security;
create policy "Users can read their own todos"
on public.todos for select
using (auth.uid() = user_id);
create policy "Users can create their own todos"
on public.todos for insert
with check (auth.uid() = user_id);
```
Then generate a migration from it:
```bash
supabase db schema declarative sync -f initial-schema
```
This compares your declared schema files against your (currently empty) migration history and writes the difference as a migration file in `supabase/migrations/`. The command then offers to apply the migration to your local database. Pass `--apply` or `--no-apply` to skip the prompt in scripts. The global `--yes` flag also applies it. Without one of those flags, a non-interactive run (CI, or an agent without a terminal) writes the file and silently skips the apply step.
The `db schema declarative` commands require [`pg-delta`](/docs/guides/local-development/diff-engines), which `supabase init` enabled for you (`[experimental.pgdelta] enabled = true` in `config.toml`). For the full declarative workflow, including managing views and functions and known caveats, see [Declarative database schemas](/docs/guides/local-development/declarative-database-schemas).
**Option B: Write the migration directly**
```bash
supabase migration new initial-schema
```
This creates an empty file at `supabase/migrations/<timestamp>_initial-schema.sql`. Write your SQL in it, then apply:
```bash
supabase db reset
```
### Step 4: Add seed data
Create `supabase/seed.sql`:
```sql title="supabase/seed.sql"
-- Create a test user (Supabase Auth)
-- Note: this is a placeholder row so seeded data has a user_id to reference.
-- It has no password, so it can't be used to sign in. To create a
-- login-capable user, use the Auth admin API or the local Studio.
insert into auth.users (id, email, raw_user_meta_data)
values ('d0e3c8f0-1234-5678-9abc-def012345678', 'test@example.com', '{}');
-- Seed application data
insert into public.todos (title, user_id)
values
('Buy groceries', 'd0e3c8f0-1234-5678-9abc-def012345678'),
('Write documentation', 'd0e3c8f0-1234-5678-9abc-def012345678');
```
### Step 5: Verify
```bash
supabase db reset
```
Drops everything, applies migrations, runs seed. If this passes, your project is reproducible.
### Step 6: Commit
```bash
git add supabase/
git commit -m "add supabase local development setup"
```
## The daily workflow
Both starting points converge here. You have a working `./supabase` directory in your repo. Here's how day-to-day development works.
### Making schema changes
Which approach you use is a project-level decision, set when you first created your schema - not a per-change choice. It depends on whether you keep declarative files in `supabase/schemas/`. Pick the tab that matches your project.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="declarative"
queryGroup="schema-approach"
>
<TabPanel id="declarative" label="Declarative schemas">
These steps assume the `pg-delta` engine. On the legacy `migra` engine, generate the migration with `supabase db diff` instead of `db schema declarative sync`, and follow [Declarative schemas on the legacy `migra` engine](/docs/guides/local-development/declarative-database-schemas#declarative-schemas-on-the-legacy-migra-engine).
1. Edit your schema file(s) in `supabase/schemas/` (add a table, a column, a policy, etc.)
2. Generate a migration: `supabase db schema declarative sync -f add-due-date-to-todo`
3. Review the generated migration file(s). See [Cleaning up generated migrations](#cleaning-up-generated-migrations)
4. Verify the full chain: `supabase db reset`
5. Commit the schema file and the migration(s) together
<Admonition type="caution">
`db schema declarative sync` compares your `supabase/schemas/` files against your existing migrations. It does not read the live local database. Changes you make directly in Studio or via SQL are invisible to it, so it reports "No schema changes found" and silently drops them. Always edit the schema files, then sync.
Don't use `db diff` to generate migrations from declarative files. Under `pg-delta`, `db diff` never uses `supabase/schemas/` as its baseline. The old `[db.migrations].schema_paths` setting no longer changes that, and the CLI warns when the setting lists any paths. Remove it from `config.toml`.
</Admonition>
</TabPanel>
<TabPanel id="imperative" label="Imperative migrations">
**If you made changes through the local Studio UI:**
```bash
supabase db diff -f add-due-date-to-todo
```
This captures your UI changes as a migration file. `db diff` compares the live local database against a shadow database built from your migrations, so anything you changed through Studio (or any SQL you ran) shows up in the diff. If you use declarative schemas, this leaves your schema files stale. Make changes by editing the files instead, per the **Declarative schemas** tab.
**If you prefer to write SQL directly:**
```bash
supabase migration new add-due-date-to-todo
```
Write the SQL in the generated file. Then verify:
```bash
supabase db reset
```
Commit the migration.
</TabPanel>
</Tabs>
### Generating types
If your app uses the generated TypeScript types, regenerate them whenever your schema changes:
```bash
supabase gen types --lang typescript --local > database.types.ts
```
Use `--linked` instead of `--local` to generate from your remote project. TypeScript is the default language; pass `--lang go`, `--lang swift`, or `--lang python` for others.
For working with the generated types (helper types, JSON inference, type-safe queries) and automating regeneration in CI, see [Generating types](/docs/guides/api/rest/generating-types).
### Staying in sync with your team
When someone else pushes new migrations:
```bash
git pull
supabase db reset
```
`db reset` replays all migrations from scratch, so you'll always match the current state of the repo.
## Pushing to a remote project
When you're ready to deploy your schema to a remote Supabase instance:
```bash
# Authenticate (if not already)
supabase login
# Link to the remote project (if not already)
supabase link --project-ref <project-id>
# Preview what will be applied
supabase db push --dry-run
# Apply migrations
supabase db push
```
`db push` applies only migrations that haven't been applied to the remote yet. It tracks this via the `supabase_migrations.schema_migrations` table created automatically on the remote database.
To also seed a fresh remote instance (dev/staging environments only):
```bash
supabase db push --include-seed
```
<Admonition type="caution">
Never use `--include-seed` on a production database. Seed data is for development and testing.
</Admonition>
### Resetting a remote dev or staging project
If a dev or staging remote drifts or gets into a messy state, you can wipe it and rebuild it from your local migrations:
```bash
supabase db reset --linked
```
Unlike the default `supabase db reset`, which targets your local database, the `--linked` flag runs against the remote project you connected with `supabase link`: it drops the remote schema, replays every local migration in order, then runs your seed files. Pass `--no-seed` to skip seeding.
<Admonition type="danger">
`db reset --linked` is destructive: it erases all data in the linked remote database. Only run it against throwaway dev or staging projects, and double-check which project you're linked to (`supabase projects list` shows the linked one) before running it. Never use it on production.
</Admonition>
For multi-environment setups with CI/CD (feature branches, staging, production), see [Managing Environments](/docs/guides/deployment/managing-environments).
## Key commands at a glance
| Command | What it does |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `supabase init` | Creates `./supabase/config.toml` |
| `supabase start` | Starts the local stack, applies migrations + seed |
| `supabase stop` | Stops the local stack (data persists until `db reset`) |
| `supabase db reset` | Destroys local DB, applies all migrations + seed from scratch |
| `supabase db reset --linked` | Destroys the **linked remote** DB and rebuilds it from local migrations + seed (destructive, dev/staging only) |
| `supabase db diff -f <name>` | Generates a migration by diffing a live database (local by default) against a shadow built from your migrations |
| `supabase db schema declarative sync` | Diffs `supabase/schemas/` against your migrations and writes the difference as new migration file(s) |
| `supabase db schema declarative generate` | Exports a live database into declarative schema files under `supabase/schemas/` |
| `supabase db pull` | Pulls remote schema into a new local migration file |
| `supabase db pull --declarative` | Updates `supabase/schemas/` from the remote database instead of creating a migration. `pg-delta` projects only |
| `supabase db push` | Applies pending local migrations to the remote database |
| `supabase db dump` | Exports remote DB schema (or `--data-only` for data) via `pg_dump` |
| `supabase migration new <name>` | Creates an empty migration file |
| `supabase migration list` | Compares local migrations against remote migration history |
| `supabase gen types --lang typescript` | Generates TypeScript types from your database schema |
| `supabase link --project-ref` | Connects local project to a remote Supabase project |
| `supabase login` | Authenticates with the Supabase platform |
For the full command reference and every flag, see the [CLI reference](/docs/reference/cli).
## Cleaning up generated migrations
When `supabase db diff` or `db schema declarative sync` generates a migration, review it before committing.
### What `pg-delta` output looks like
Generated SQL uses uppercase keywords, wrapped at a maximum width of 180 characters. You can override this with `[experimental.pgdelta] format_options` in `config.toml`, or set `format_options = "null"` to emit raw statements with no formatting applied.
Most changes produce a single migration file. When a change crosses a transaction boundary (for example `alter type ... add value` followed by a check constraint that uses the new enum value, which can't run in the same transaction), the CLI may write one ordered migration file per unit instead. The extra files carry a numeric segment suffix, such as `<timestamp>_add-status_1.sql` and `<timestamp+1s>_add-status_2.sql`. Commit all of them.
A migration whose statements can't run inside a transaction starts with this directive on its first line:
```sql
-- pg-delta: transaction=false
```
`db reset`, `db push`, and `migration up` honor it by running the file's statements without a wrapping transaction. Keep the line. The CLI detects `create index concurrently` on its own and runs it standalone even without the directive, but other statements that can't run in a transaction depend on it. The directive also changes what happens on failure. Without a wrapping transaction, a failed statement leaves the earlier statements in the file applied.
Deploys through the [GitHub integration](/docs/guides/deployment/branching/github-integration) don't honor the directive and run every migration inside a transaction, so these migrations fail there. Not every split file carries it. `alter type ... add value` runs in its own transaction, so its file is separate but has no directive.
### Extension statements
`CREATE EXTENSION IF NOT EXISTS ...` or `DROP EXTENSION ...` might appear when your local and remote extension sets differ. Keep the statement if it reflects a change you want. Remove it if the extension is already handled by a previous migration or you don't want to change it. Decide deliberately, because a `DROP EXTENSION` applies silently on `db reset`.
Objects that extensions create and manage themselves, such as partitions maintained by `pg_partman` and queue tables created by `pgmq`, are recognized as extension-managed. The diff never emits raw `create table` or `drop table` statements for them. Instead it expresses changes through the extension's own API, such as `select pgmq.drop_queue('q');` or a `delete from partman.part_config` row, and the CLI flags those statements as destructive. Review them as carefully as any other drop.
### Coverage warnings
`pg-delta` reports schema objects it doesn't track (such as casts, operators, and text search configurations) as warnings instead of silently dropping them. Add those objects through hand-written migrations. To turn these warnings into hard failures (useful in CI), pass `--strict-coverage`.
### Grants and revoke patterns
Both engines treat permissions as part of the schema state, so generated migrations can include grant statements you didn't write. New tables can come with explicit `GRANT` lines for the default roles, and a first diff against an existing database can emit long runs of `REVOKE ALL` followed by `GRANT` statements derived from default privileges. These statements can also come from default-privilege differences between the two databases, so before removing them, check that the roles already hold the intended privileges on the target database. If they do and you haven't changed permissions, the lines are safe to remove. Be consistent across your team about whether you keep or remove them.
### Known limitations of `db diff`
No diff engine captures everything. DML (INSERT, UPDATE, DELETE) is never tracked, so you must add data changes to the migration by hand. This includes storage buckets, which are rows in the `storage.buckets` table rather than schema objects. See the [full list of caveats](/docs/guides/local-development/declarative-database-schemas#known-caveats) in the declarative schemas guide.
If a diff looks wrong, you can fall back to the legacy engine for a single run (`db diff --use-migra`, or `db pull --diff-engine migra`) to compare, or opt out entirely with `enabled = false` under `[experimental.pgdelta]`. See [Diff engines](/docs/guides/local-development/diff-engines) for how engine selection works and what differs between the two.
Treat generated output as a draft, not a final migration. When in doubt, review the generated SQL and adjust it manually.
## Troubleshooting
**`db reset` fails with a migration error**
The output will show which migration file failed and the SQL error. Fix the migration file, then run `db reset` again.
**`db push` says migrations are already applied**
The remote database already has those migrations in its history. Run `supabase migration list` to compare local vs. remote state. If they're out of sync, use `supabase migration repair` to correct the remote history.
**Schema drift: remote was changed outside of migrations**
If someone modified the remote database directly (via Dashboard, SQL editor, etc.), run `supabase db pull` to capture those changes as a new migration file. Then `supabase db reset` locally to verify everything still works.
**`db pull` prints "No schema changes found" and exits non-zero**
Local and remote are already in sync, so there is nothing to pull. If you script `db pull` in CI, expect a non-zero exit code. An empty `db diff` exits 0 instead.
**`db diff` warns that `schema_paths` is ignored**
Under `pg-delta`, declarative files are never part of the `db diff` baseline, so `[db.migrations].schema_paths` has no effect on it. Generate migrations from declarative files with `supabase db schema declarative sync` instead.
**The first pull after enabling `pg-delta` is unexpectedly large**
This happens when your migration history was built from legacy `migra` diffs, which didn't track objects such as comments, domains, roles, and publication membership. The first `db pull` on `pg-delta` captures those objects in a one-time catch-up migration. Your database already has them, so accept the prompt to record the migration as applied and review the file like any other generated migration. If your baseline came from the legacy engine's `pg_dump` path instead, it already contains those objects, and the first pull reports "No schema changes found". See [Switch an existing project to `pg-delta`](/docs/guides/local-development/diff-engines#switch-an-existing-project-to-pg-delta) for the full procedure.
**A diff looks wrong or comes back empty unexpectedly**
Set `PGDELTA_DEBUG=1` and rerun the command. The CLI writes a debug bundle with the extracted snapshots, plan, and diagnostics. Plan bundles land under `supabase/.temp/pgdelta/v2/debug/<id>/`, and bundles from failed runs land under `supabase/.temp/pgdelta/debug/<id>/`. The bundle shows what the engine saw. Attach it when filing a CLI bug report.
**Docker issues on `supabase start`**
Ensure Docker is running and has at least 7 GB of RAM allocated. If containers fail health checks, try:
```bash
supabase stop
supabase start
```
If problems persist, `supabase stop --no-backup` for a clean restart (this removes local database data).