Skip to main content
pgschema manages schema state, not data. When a schema change requires touching existing data (e.g. backfilling a new column), declare the end state in your schema file, then edit the generated plan to insert the data migration steps. This works because the plan’s source_fingerprint pins the database state the plan was generated against, not the plan’s SQL. You can freely edit the steps; pgschema apply still detects if the database has drifted since planning.

Example: add a NOT NULL column with backfill

Desired state in schema.sql:
1

Generate Plan

The generated plan adds the column directly, which would fail on a non-empty table:
2

Edit Plan

Rewrite the step to add the column as nullable first, insert a backfill step, then enforce the constraint. Only the sql field is required for hand-added steps:
Steps within a group execute in a single transaction, so the backfill and constraint either all succeed or roll back together.
3

Apply Plan

After apply, the database matches schema.sql, so subsequent plans are empty — the data migration naturally runs only once.
For large tables, run the backfill in batches outside the transaction to avoid long locks. Put ADD COLUMN in one plan, batch the UPDATE with your own tooling, then run a second plan for SET NOT NULL. See Online DDL for lock-aware patterns.
The same technique applies to any change the diff engine cannot infer, such as rewriting a rename from DROP + ADD into ALTER TABLE ... RENAME, or seeding lookup-table rows alongside a new table.