Phase 1: Extract
The extractor connects to your PostgreSQL database and queries the system catalogs (pg_catalog) to build a complete schema representation.
What Gets Extracted
- Tables - Columns, constraints, indexes, partitioning, row security flags and policies
- Views - Regular and materialized views with definitions
- Functions - All languages, with full body and attributes
- Triggers - Table triggers with conditions
- Types - Enums, composites, domains
- Sequences - With ownership information
- Extensions - Installed extensions
- TimescaleDB - Hypertables, compression/retention policies, continuous aggregates
Excluded Schemas
System schemas are automatically excluded:
Use
--exclude-schema to exclude additional schemas (e.g., _prisma, supabase_*).
Phase 2: Parse
The parser reads your SQL files and converts them to the same JSON schema format as the extractor.Supported SQL
pgtofu supports standard PostgreSQL DDL:Type Normalization
Types are normalized for accurate comparison:Phase 3: Diff
The differ compares the current schema (from extract) with the desired schema (from parse) to detect all differences.Change Detection
For each object type, the differ:- Builds maps by qualified name (
schema.name) - Detects additions (in desired but not current)
- Detects deletions (in current but not desired)
- Detects modifications (in both but different)
Change Severities
Safe Type Widening
These type changes are classified as SAFE:View Comparison
View definitions are normalized before comparison to handle formatting differences (whitespace, case, aliases). When statically identifiable output columns change names at the same ordinal, pgtofu emits explicitALTER VIEW ... RENAME COLUMN statements before replacing
the definition. Changes whose prior definitions depend on other changed objects
remain in one migration batch, with tables and views restored before dependent
indexes, functions, and triggers in down migrations.
Policy Comparison
Policies are identified by their table and name. pgtofu compares the command, permissive or restrictive mode, role set, both expressions, and comments. Expression comparison handles common PostgreSQL catalog rewrites while preserving literal case, quoted identifiers, and meaningful casts. See row-level security for declarations and migration behavior.Phase 4: Generate
The generator creates golang-migrate compatible migration files from the detected changes.Dependency Resolution
Changes are ordered using topological sort (Kahn’s algorithm):- Extensions
- Custom types
- Sequences
- Tables
- Columns, constraints
- Indexes
- Views
- Functions
- Triggers
- TimescaleDB features
Generated DDL Features
- Idempotent - Uses
IF EXISTS/IF NOT EXISTSclauses - Transaction-wrapped - Grouped in
BEGIN/COMMITblocks when safe - Reversible - Both up and down migrations generated
- Documented - Includes comments describing each change
- Safe identifiers - All identifiers properly quoted
File Naming
Breaking Change Warnings
Schema File Organization
Recommended directory structure:.sql files recursively and reads them in filename order, with files under policies/ read after the other schema files.
Keep each table’s policy declarations after its table definition, or put them in policies/.
Best Practices
One table per file
One table per file
Include the table definition and its indexes in the same file for easier maintenance.
Explicit constraint names
Explicit constraint names
Use
CONSTRAINT fk_orders_user FOREIGN KEY... instead of anonymous constraints for clearer migrations.Version control your schema
Version control your schema
Commit schema files to git. They are the source of truth for your database structure.
Preview before generating
Preview before generating
Always run
pgtofu diff before pgtofu generate to review changes.See Also
- Quickstart - Step-by-step tutorial
- PostgreSQL Features - Supported features
- TimescaleDB Features - TimescaleDB support