Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Configuration reference

Everything in pgevolve.toml. The capability spec lists what's implemented vs. planned; this file walks through how to use what's implemented today.

pgevolve init creates a minimal config; this guide explains each section.

File location

By default pgevolve loads ./pgevolve.toml. Override with --config <path> on any command.

[project]

[project]
name           = "myapp"            # informational
schema_dir     = "schema"           # relative to the config file
plan_dir       = "plans"            # relative to the config file
layout_profile = "schema-mirror"    # one of the built-ins, or a path
KeyDefaultNotes
name— (required)Shown in --help and logs. Purely informational.
schema_dir"schema"Where source *.sql lives. Resolved relative to the config file.
plan_dir"plans"Where pgevolve plan writes new plan directories.
layout_profile"schema-mirror"One of schema-mirror, kind-grouped, feature-grouped, free-form, or a path to a *.toml file declaring a custom profile. See the spec.

[managed]

[managed]
schemas        = ["app", "billing"]
ignore_objects = ["app.legacy_etl_*", "billing.audit_*"]
KeyDefaultNotes
schemas[]List of schema names pgevolve is responsible for. Anything outside this list is ignored by diff, plan, and apply. An empty list means "no schemas are managed" (lint will not enforce schema match).
ignore_objects[]Qname or glob patterns to exclude even within managed schemas. Useful for legacy tables that aren't yet pgevolve-controlled.
min_pg_version14Minimum PG major version the project targets. Accepted values: 14, 15, 16, 17, 18. Gates PG-version-specific source features (e.g., publication row filters need PG 15+).

The [managed] filter is the safety net. Even if your source tree declares only one table, an unfiltered apply would emit drops for every catalog object outside your control. The filter prevents that.

[planner]

[planner]
strategy = "online"   # "atomic" | "online"
KeyDefaultNotes
strategy"online""atomic" puts everything in one transaction and disables every online rewrite; "online" enables the configured rewrites (next section).

[planner.online_rewrites]

[planner.online_rewrites]
create_index_concurrent       = true
fk_not_valid_then_validate    = true
check_not_valid_then_validate = true
not_null_via_check_pattern    = true
refresh_mv_concurrently       = true
view_drop_create_dependents   = true

Each switch defaults to true. Setting any to false disables that specific rewrite without dropping to atomic mode. Useful for environments where you want online behavior in general but need to opt out of one pattern (e.g., a managed-service Postgres that disallows CONCURRENTLY).

SwitchRewriteSpec
create_index_concurrentNon-unique CreateIndex on an existing table → CREATE INDEX CONCURRENTLY. Same for DropIndex.indexes.md
fk_not_valid_then_validateADD FOREIGN KEY on an existing table → ADD ... NOT VALID + VALIDATE CONSTRAINT (two transaction groups).pipeline.md
check_not_valid_then_validateSame shape, for CHECK constraints.pipeline.md
not_null_via_check_patternSET NOT NULL on a populated column → four-step ADD CHECK NOT VALID / VALIDATE / SET NOT NULL / DROP CHECK.pipeline.md
refresh_mv_concurrentlyUpgrade REFRESH MATERIALIZED VIEW to REFRESH … CONCURRENTLY when the MV has at least one unique index. Has no effect under strategy = "atomic".cli.md
view_drop_create_dependentsWalk the body_dependencies graph and emit explicit DROP + CREATE steps for every view transitively affected by an upstream change. When false, the planner errors instead of cascading dependent-view recreations.cli.md

[environments.<name>]

[environments.dev]
url      = "postgres://localhost/myapp_dev"

[environments.prod]
url_env  = "DATABASE_URL_PROD"        # read DSN from env var (recommended)
strategy = "online"                   # overrides [planner].strategy for --db=prod

[environments.test]
url      = "postgres://localhost/myapp_test"
strategy = "atomic"                   # opt-out for fast / hermetic test DB
KeyNotes
urlExplicit DSN. Mutually exclusive with url_env.
url_envName of an environment variable holding the DSN. Read at command time. Mutually exclusive with url.
strategyOptional per-environment override of [planner].strategy.

Omit both url and url_env to fall through to PGEVOLVE_DATABASE_URL and libpq env vars (PGHOST, PGUSER, etc.).

Connection precedence

For pgevolve <cmd> --db <env>:

  1. --url <dsn> (CLI argument)
  2. [environments.<env>].url
  3. [environments.<env>].url_env
  4. PGEVOLVE_DATABASE_URL
  5. libpq env vars
  6. ~/.pgpass

[shadow]

[shadow]
backend          = "auto"         # auto | testcontainers | dsn
url              = "postgres://localhost/myapp_shadow"   # for backend = "dsn"
url_env          = "PGEVOLVE_SHADOW_URL"                 # alternative to url
reset            = "drop_schema_cascade"                 # drop_schema_cascade | none
extensions       = ["pgcrypto", "uuid-ossp"]
postgres_version = "17"           # major version; used to select container or validate DSN
KeyDefaultNotes
backend"auto"How to obtain a shadow Postgres. See below.
urlDSN for an existing Postgres to use as shadow. Requires backend = "dsn" or backend = "auto" with a URL set. Mutually exclusive with url_env.
url_envName of an environment variable holding the shadow DSN. Alternative to url.
reset"drop_schema_cascade"How to clean the shadow DB between runs. "drop_schema_cascade" drops all schemas under [managed].schemas; "none" leaves the DB as-is (useful for DSN backends where you manage teardown yourself).
extensions[]Extensions to install in the shadow DB before any apply. Names must match [a-zA-Z_][a-zA-Z0-9_-]*.
postgres_version"16"Major version: "14", "15", "16", or "17". Pick the version that matches production. Used to select the container image or to validate a provided DSN.

backend values

  • "auto" (default): uses url / url_env if set; otherwise tries testcontainers if Docker is available; otherwise errors with a helpful message.
  • "testcontainers": always uses Docker. Hermetic; requires Docker to be running. Pulls postgres:<major>-alpine.
  • "dsn": connects to a user-supplied Postgres. No Docker required. Useful for developers without Docker or for projects with pre-installed extensions (TimescaleDB, PostGIS, etc.).

pgevolve validate --shadow and the --shadow-validate flag on plan / diff / validate all read this block. Without it those flags error out with a helpful message.

[[lint_waiver]]

[[lint_waiver]]
rule   = "column-position-drift"
target = "app.users"
reason = "applied via separate rewrite-table operation; see PR #234"

[[lint_waiver]] rows acknowledge LintAtPlan-severity findings so that pgevolve plan doesn't refuse with exit 2. Live in the plan's intent.toml, not in pgevolve.toml.

KeyNotes
ruleExact rule name of the finding to waive (e.g., "column-position-drift"). Must be non-empty.
targetSubstring matched against the finding's message (typically the qualified object name). Must be non-empty.
reasonFree-form justification. Shown in --format human output.

Match semantics: a waiver applies when rule equals the finding's rule name and target is a substring of the finding's message. A waiver that matches zero findings is reported as a warning ("unused waiver").

Preflight at apply time validates structural well-formedness: both rule and target must be non-empty strings. A malformed waiver row causes preflight to exit 2.

Multiple [[lint_waiver]] rows are supported — use one per finding.

[cluster]

[cluster]
project = "../my-cluster"

Optional. Links this per-DB project to a sibling cluster project (managed via pgevolve cluster … against a pgevolve-cluster.toml). When set, cluster-aware lints (e.g. grant-references-unknown-role) cross-check grantee role names against the linked cluster project's declared roles.

KeyNotes
projectPath to the cluster project directory (containing pgevolve-cluster.toml). Relative paths resolve against pgevolve.toml's directory.

See docs/spec/cluster.md for the cluster surface and docs/spec/grants.md for the cross-check rules.

Worked example: production-grade config

[project]
name           = "ledger"
schema_dir     = "schema"
plan_dir       = "plans"
layout_profile = "schema-mirror"

[managed]
schemas        = ["app", "billing", "audit"]
ignore_objects = ["audit.legacy_*"]

[planner]
strategy = "online"

[planner.online_rewrites]
# Production DB doesn't allow CONCURRENTLY (RDS-style).
create_index_concurrent       = false
fk_not_valid_then_validate    = true
check_not_valid_then_validate = true
not_null_via_check_pattern    = true

[environments.dev]
url      = "postgres://localhost/ledger_dev"
strategy = "atomic"               # fast local iteration

[environments.staging]
url_env  = "DATABASE_URL_STAGING"

[environments.prod]
url_env  = "DATABASE_URL_PROD"

[shadow]
backend          = "testcontainers"
postgres_version = "16"

CI runs pgevolve validate --shadow on every PR. The dev developer runs pgevolve plan --db dev, then pgevolve plan --db staging once the diff stabilizes; the same plan directory is applied to staging and production after intent-file approval.