Last Updated: 2026-04-24
Audience: Developers and AI agents working on Postgres schema for Encore OS. This document is the canonical reference for maintaining DDL under
supabase/schemas/ and generating versioned migrations under supabase/migrations/ with the Supabase CLI.
Goals
- Single source of truth for DDL:
supabase/schemas/**/*.sqldescribes the desired database shape (tables, types, routines, policies where appropriate). - Reproducible promotion:
supabase/migrations/*.sqlremains whatsupabase db reset, CI, and remotes apply. For normal DDL, those files are produced bysupabase db diff, not by hand. - Tooling:
@supabase/pg-deltaand@supabase/pg-topofrom pg-toolbelt assist with export, optional apply-to-empty-DB experiments, and ordering diagnostics. They are not a substitute forsupabase db diffin this repo.
Backfill status (2026-05-29): the existing schema has not yet been bulk-exported intosupabase/schemas/**— pg-delta’s declarative export does not scale to the full ~1256-table schema. Until a scalable backfill lands, migrations (05-28 baseline + forward) are the operative source of truth; author new DDL declaratively and letsupabase db diffgenerate migrations.
Directory layout
After a successfulnpm run db:schemas:export, pg-delta typically emits a tree similar to:
pg-delta already groups by object type. If you split DDL manually, name files so dependencies are satisfied (e.g. base tables before foreign keys that reference them). @supabase/pg-topo validates statement-level ordering when you run npm run db:schemas:lint.
Column ordering: When altering existing tables in exported files, append new columns at the end of the table definition where possible to reduce noisy diffs on re-export.
Environment variables
npm scripts (root package.json)
Alpha warning:@supabase/pg-deltais pre-1.0. Pin versions inpackage.json; use export/lint locally/CI, avoiddeclarative-applyagainst production.
First-time bootstrap (populate supabase/schemas/)
When Docker and the local Supabase DB port (54322 per supabase/config.toml) are available:
.sql tree. Until then, only supabase/schemas/README.md may be present — db:schemas:lint intentionally no-ops when there are zero .sql files.
Day-to-day DDL workflow
- Change DDL in
supabase/schemas/(not insupabase/migrations/). - Optional:
npm run db:schemas:lint— catches parse errors / unresolved dependencies / cycles viapg-topo. - Stop local Supabase:
npx supabase stop(required beforedb diffin this workflow). - Generate migration:
npx supabase db diff -f <snake_case_description>
Shortcut:npm run db:schemas:diff -- -f <snake_case_description> - Review the generated SQL under
supabase/migrations/— watch for unintendedDROPs. - Start + reset:
npx supabase start && npx supabase db reset - Validate:
npm run validate-migration -- --latest - Test RLS:
npm run test:rls(and other suites per change risk).
schema lane) unless explicitly marked / named for system-defaults DML.
Bounded declarative pilot (2026-06-09)
- Tooling decision: the pilot uses native
supabase db diff --use-pg-delta— confirmed supported on the project’s Supabase CLI (the--use-pg-deltaflag is present insupabase db diff). The standalone@supabase/pg-deltawrapper (npm run db:schemas:*) remains a proven, CLI-version-independent fallback. - Scope: NEW tables in the FA core only (non-regulated). Author the table’s DDL as
supabase/schemas/fa/<table>.sql, generate the migration viasupabase db diff --use-pg-delta -f <name>, review for unintendedDROPs, apply locally withsupabase migration up, then runnpm run validate-migration -- --latest+npm run test:rls. Migrations remain the promotion source of truth. - Excluded: regulated cores (CL/PM/HR/RH/GR) and any bulk export of the existing ~1256-table schema (pg-delta export does not scale to it).
- Round-trip validation: COMPLETE (2026-06-09). The pg-delta engine was validated end-to-end against an isolated throwaway Postgres 17 (no shared stack touched):
pgdelta catalog-exportsnapshots the current schema → a newfa_pilot_demotable is authored →pgdelta plan --source <snapshot.json> --target <db> --format sql --integration supabaseemits the migration. Result (4/4 checks): a clean additive migration (CREATE TABLE public.fa_pilot_demo+ PK, annotated-- Risk: safe) that does not drop the pre-existingfa_existingtable, applies cleanly to a fresh copy of the prior state, and converges (re-diff after apply is empty). No coverage gaps for basic table DDL (uuid PK, columns, defaults).- Recommended pattern for this repo’s partial-declarative reality:
pgdelta catalog-exportthe current state → apply the change to a DB →pgdelta plan --source <snapshot.json> --target <db>for the incremental migration. Both sides are complete states, so no spurious drops. - Caveat (the documented scaling limit):
supabase db diff --use-pg-deltacompares the WHOLEsupabase/schemas/desired state against the migration shadow — with an incomplete declarative tree it will propose dropping undeclared objects. Until the full schema is exported (it does not scale to ~1256 tables), prefer the snapshot→plan pattern above, or keep a complete declarative tree. - ⚠️ Scaling spike result (2026-06-09) — the pilot path is gated on a performance fix. A measured spike against the live 1282-table local schema (not the 2-table throwaway the round-trip above used) found that every pg-delta entry point hangs on the full catalog read:
pgdelta catalog-exportdid not finish in ~9.5 min, and--filter-scoped reads (export and catalog-export) did not finish in 5–6 min — the filter is applied after the read, so it cannot prune the cost. Becausesupabase db diff --use-pg-deltadrives the same engine over the same catalog, the “first real pilot step” is expected to hang the same way at this schema size. Do not treatdb diff --use-pg-deltaas the day-to-day authoring path yet; keep authoring new DDL as hand-reviewed migrations. Seedocs/superpowers/specs/2026-06-09-declarative-pgdelta-export-scaling-spike.md. Re-spike gate:catalog-exporton a ~1300-table DB completing in < 60s.
- Recommended pattern for this repo’s partial-declarative reality:
- Read-only drift report: the
catalog-drift-reportjob insupabase-drift-check.ymlis non-blocking and complements the existing migration-ledger drift check; it also refreshesreports/db/catalog-snapshot.prod.json. - Never
declarative-applyagainst a hosted remote.
Lovable-originated schema changes
Lovable can apply DDL directly to hosted DEV, which bypasses local migration authoring. Treat this as a drift-capture workflow:- Run
npm run db:pull-from-dev -- --name <descriptive_slug>to generate a migration from hosted DEV changes. - Review the generated migration under
supabase/migrations/for safety, lane correctness, and constitution requirements. - If local Supabase is available, replay locally (
npx supabase start && npx supabase db reset) and runnpm run db:schemas:exportsosupabase/schemas/stays aligned. - Run
npm run validate-migration -- --latestandnpm run test:rlsbefore merging.
supabase/migrations/*.sql remains the promotion source of truth; declarative files are an audit/authoring surface, not a production deployment shortcut.
When not to rely on declarative + db diff alone
Keep explicit migrations (hand-authored or carefully reviewed) for cases that diff tools handle poorly (aligns with Supabase / migra limitations):
Rollbacks
- Revert or edit the declarative
.sqlto the prior desired state. npx supabase stopnpx supabase db diff -f <describe_rollback>- Review carefully for data loss (
DROP COLUMN, narrowing types, etc.). - Apply locally (
db reset) and run tests before opening a PR.
Relationship to constitution / platform rules
- §5 Migrations only — still true: production schema changes ship as
supabase/migrations/*.sql. - RLS + tenant isolation — declarative SQL must follow the same patterns as migrations (
SECURITY DEFINERhelpers, no recursive policies,organization_idon business tables). See DATABASE_DEVELOPMENT_GUIDE.md and.cursor/rules/database-patterns.mdc. - PF-96 / multi-tenant literals — unchanged; declarative workflow does not relax jurisdiction or tenant-configurability rules.
See also
- SUPABASE_CLI_LOCAL_WORKFLOW.md — CLI-first operations + declarative section
- MIGRATION_LANES.md — CI migration lane policy
- supabase/schemas/README.md — short operator checklist