Skip to main content

pgdeclare

pgdeclare is a Postgres schema differ.

Why we built it: Your Database Schema Should Be Code You Can Read.

Overview

Your database schema should be code you can read.

With migration files, reviewers read deltas, not the schema, and an LLM reading the repository has to reconstruct the state too, getting it wrong in the same places people do. pgdeclare keeps the schema as declarations: one CREATE TABLE per table, in plain SQL files, always describing the database as it is now. You change a table by editing its definition. pgdeclare builds the declared schema in a scratch database, diffs it against the live one through pg_catalog, and applies the difference.

A diff cannot infer everything, such as a rename or a backfill. For those you write small imperative @migrate-before / @migrate-after blocks next to the DDL they belong to. Each runs once per database and is recorded in a hash ledger, so an edit after it ran is refused rather than silently ignored. Destructive changes on tables that hold data need an explicit @drop directive. Migrations that have run can be pruned so they don't pile up. History still exists, in git, where history belongs.

The schema files are built into a scratch plan database and introspected. Pending @migrate-before blocks run on the live database, which is then introspected. The two are diffed, the difference is applied, @migrate-after and DO blocks run, and a second diff must come back empty.

Status: 0.1.1. The engine has run the production schema of Powertime since it went into service on 10 December 2025. Backwards compatibility may not be preserved until 1.0.

Design choices

  • Postgres parses your SQL. The desired schema is not reconstructed from a parser's model of your files. pgdeclare executes them in a scratch "plan" database and introspects the result, so defaults, implicit constraints and type normalisation come out exactly as Postgres would produce them.
  • Postgres orders the work. Statements, and the changes themselves, run retry-until-stable: anything that fails on a missing dependency is retried after the rest, until everything succeeds or no progress is made. You do not maintain a dependency order.

Install

pip install pgdeclare
# or, inside a uv project
uv add pgdeclare

Requires Python 3.12+ and a PostgreSQL 14 to 18 server.

Connect as a role that owns the target database's schemas. Each run builds your schema in a scratch plan database on the same server, which by default is created and dropped per run, so the role needs CREATEDB. Without it, create a plan database you own (createdb -O app app_plan) and pass --plan-database app_plan.

Quickstart

A schema tree:

sql/
└── schemas/
    ├── 01_types.sql
    └── 02_note.sql
-- sql/schemas/01_types.sql
CREATE TYPE mood AS ENUM ('calm', 'loud');
-- sql/schemas/02_note.sql
CREATE TABLE IF NOT EXISTS note (
    id     bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    author text NOT NULL,
    body   text NOT NULL,
    mood   mood NOT NULL DEFAULT 'calm'
);

CREATE INDEX IF NOT EXISTS note_author_idx ON note (author);

Command line

export DATABASE_URL=postgresql://app:secret@localhost:5432/app

pgdeclare diff                 # what would change, as a summary plus SQL
pgdeclare diff --json          # the same, machine-readable
pgdeclare diff --exit-code     # exit 2 when anything is pending (for CI)
pgdeclare apply --dry-run      # plan only, nothing written
pgdeclare apply --dry-run --prove   # rehearse the real run on a throwaway clone
pgdeclare apply                # converge the database

Now add a column and a backfill:

-- sql/schemas/02_note.sql
CREATE TABLE IF NOT EXISTS note (
    id     bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    author text NOT NULL,
    body   text NOT NULL,
    mood   mood NOT NULL DEFAULT 'calm',
    title  text
);

CREATE INDEX IF NOT EXISTS note_author_idx ON note (author);

-- @migrate-after:2026-01-15_note_titles
UPDATE note SET title = left(body, 40) WHERE title IS NULL;
-- @end-migrate

pgdeclare apply adds the column, then runs the block once and records it.

Every command, flag and exit code is in docs/cli.md; the library API is in docs/python.md.

Documentation

  • Command line: flags, exit codes, --prove.
  • Python API: the library entry points and their errors.
  • Schema files: directory layout, @migrate-before / @migrate-after, @drop, DO blocks.
  • Coverage: every object kind pgdeclare manages, how, and what it leaves alone.
  • Configuration: every PgDeclareConfig field.
  • Safety: previews, --prove, online/offline/blocked changes, quoting, masking, and the supported PostgreSQL versions (14 to 18).

How it compares

As of October 2026, pgdeclare manages nearly every object kind pg-delta and pgschema do; the exact differences are in Coverage. pgdeclare's difference is where the steps a diff cannot infer live: next to the DDL they belong to, run once, and recorded by hash.

Tool Renames and backfills Records what ran Free licence Notes
pgdeclare @migrate blocks beside the DDL Hash ledger; edited blocks are refused Apache-2.0 Re-diffs after applying and fails the run if anything is left; --prove rehearses the full run, @migrate blocks included, on a clone
pgschema Outside the tool No; state only Apache-2.0 Terraform-style plan and apply; broad object coverage
pg-delta Renames come out as drop plus add, edited by hand; never writes row changes Through Supabase migration files PostgreSQL Alpha; very broad coverage; prove tests a plan on a clone first
Atlas Versioned migration files Yes, in versioned mode Apache-2.0 Community Edition The Community Edition omits views, functions, triggers and RLS policies; the full binary is under the Atlas MSA
pg-schema-diff Renames come out as drop plus add No MIT Strong lock avoidance for online changes
migra Not handled No Unlicense Deprecated; last release 2022

Contributing

Issues and pull requests are welcome on GitHub. See CONTRIBUTING.md for running the tests, and SECURITY.md for reporting vulnerabilities.

License

Apache License 2.0. See LICENSE and NOTICE.

Extracted from Powertime, where it manages the application's schema.

Metadata

Release files for pgdeclare 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pgdeclare 0.1.1
File Size Uploaded
pgdeclare-0.1.1.tar.gz 252.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pgdeclare 0.1.1
File Interpreter ABI Platform
pgdeclare-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 399.8 kB

Release files / pgdeclare-0.1.1.tar.gz

Download URL pgdeclare-0.1.1.tar.gz
Size 252.6 kB
Tags Source
SHA-256 checksum
How to use checksums
44879e2c5a08978f00340b33253cd77bfd8158af523ebfbbd854f6a6e0aecb64
BLAKE2b-256 checksum
How to use checksums
1dbc78331016730d0404e8a4603d082a38cada8d0c8012e157168ffc837fd689
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release files / pgdeclare-0.1.1-py3-none-any.whl

Download URL pgdeclare-0.1.1-py3-none-any.whl
Size 147.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c019f79718e17264c84c063683ea21e3046eead234fb061f4da74941b2a56163
BLAKE2b-256 checksum
How to use checksums
89b1bffaa7d1b5eb99f116209a3b1c8f0f68d51000993976daa7f854a3f586a5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 18, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page