ALTER TABLE, CREATE INDEX, or DROP COLUMN by hand, you describe the schema you want — the end state — in plain SQL CREATE statements. supaschema reads those files, compares them against what already exists, and generates the precise SQL needed to close the gap. You own the intent; supaschema owns the diff.
Imperative vs. Declarative Migrations
Most teams start with imperative migrations: a numbered sequence of hand-authored SQL files that each describe a change. This works well at first, but the sequence accumulates quickly. Reviewing migration number 0187 requires understanding all 186 that came before it to know what the schema actually looks like today. Declarative schema management inverts this model.Imperative (traditional)
You write
ALTER TABLE orders ADD COLUMN status text. You track every change as a new file. The current schema is implicit — spread across hundreds of migration files.Declarative (supaschema)
You write
CREATE TABLE orders (id bigint, status text). The current schema is explicit — always visible in your source files. supaschema generates the ALTER TABLE for you.Schema Sources: SQL Files on Disk
A schema source is any collection of SQL files that supaschema can parse into a complete picture of your database objects. The most common source is a directory of.sql files referenced by the schemaPaths configuration key (or the dir: source prefix on the CLI).
CREATE statements — one file per table, one file per domain, or however you prefer to organise them. supaschema does not impose a file-naming convention.
- Tables — columns, constraints, defaults, identity columns
- Views and materialised views
- Functions and procedures
- Types — composite types, domains, ranges
- Enums
- Indexes — including partial and expression indexes
- Row-level security policies
- Grants and privileges
- Triggers and rules
- Extensions (within managed schemas)
supaschema uses PostgreSQL’s own parser compiled to WebAssembly (WASM) to tokenise and validate your SQL files. This means your schema is parsed with 100 % PostgreSQL fidelity — no regex shortcuts, no home-grown grammar — and it works without a running database instance.
The SchemaModel: A Parsed Snapshot
After parsing all source files, supaschema builds a SchemaModel — an in-memory, structured representation of every object it found. Think of it as a catalogue snapshot: each object carries its type, qualified name, normalised definition, and a deterministic content hash.Fingerprinting and Drift Detection
Every SchemaModel carries a fingerprint — a single deterministic hash derived from the hashes of all its constituent objects. If anything in the schema changes — a column type, a function body, a policy expression — the fingerprint changes.Why Idempotency Matters
A migration is idempotent when it can be applied more than once without error and without changing state after the first application. supaschema generates idempotent SQL by default — using constructs likeCREATE TABLE IF NOT EXISTS, ALTER TABLE … ADD COLUMN IF NOT EXISTS, and DO $$ … $$ blocks where necessary.
Idempotency matters for several practical reasons:
- Retries are safe. If a deployment fails halfway through and the runner re-applies the migration file, it will not produce duplicate-object errors or corrupt data.
- CI verification is straightforward. The
verifycommand applies the migration twice against a throwaway database to confirm idempotency. If the second application changes any state, the verification fails. - Partial rollouts stay consistent. In multi-region setups where the same migration file is applied to several database instances, each application is guaranteed to converge to the same final state regardless of order or repetition.
Managed Schemas in Supabase
When you deploy on Supabase, several schemas are owned and managed by the platform itself. supaschema blocks declarative ownership of objects in all of these schemas:auth, storage, realtime, vault, extensions, cron, net, supabase_functions, graphql, graphql_public
This protection exists because:
- The platform migrates these schemas on your behalf during Supabase version upgrades.
- Dropping or altering a platform-managed function (such as
auth.uid()) can break authentication across your entire project. - Your schema files should express only the objects you own.
You can still reference managed schema objects (foreign keys to
auth.users, calls to auth.uid() in policies, etc.). supaschema only prevents you from declaring ownership of objects inside those schemas.