Skip to content

Quick Start

By the end of this guide, you’ll change one file and watch pgmt automatically cascade changes through your entire dependency graph. Takes about 10 minutes.

curl -fsSL https://pgmt.dev/install.sh | sh

Other methods (npm, cargo) and prerequisites are on the installation page. You’ll need Docker running — pgmt uses it for the shadow database.

pgmt requires PostgreSQL 13 or later. Choose your preferred option:

Docker (Recommended):

docker run -d \
--name pgmt-dev \
-e POSTGRES_PASSWORD=dev \
-e POSTGRES_DB=myapp_dev \
-p 5432:5432 \
postgres:18

Already have PostgreSQL? Just create a database:

createdb myapp_dev
# Create project directory
mkdir my-app && cd my-app
# Initialize pgmt (Docker users)
pgmt init --dev-url postgres://postgres:dev@localhost/myapp_dev --defaults
# For local PostgreSQL without password
# pgmt init --dev-url postgres://localhost/myapp_dev --defaults

This creates:

my-app/
├── schema/ # Your SQL schema files
├── migrations/ # Generated migration files
├── schema_baselines/ # Baseline snapshots (created on-demand)
└── pgmt.yaml # Configuration

Want more control? Run pgmt init without --defaults for an interactive setup that:

  • Detects your PostgreSQL version and installed extensions, and warns when the stock shadow image won’t work (e.g. PostGIS) with a prompt for a custom image
  • Detects schemas your shadow image provides (PostGIS’s topology, Supabase’s auth, …) and offers to exclude them from management
  • Shows you exactly what’s in your database (if importing existing schema)
  • Lets you choose what the generated schema files include (grants, triggers, …)
  • Automatically validates generated schema files and explains dependency issues

Create a simple table:

cat > schema/users.sql << 'EOF'
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL,
full_name TEXT NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
EOF

Now add a view that depends on it:

cat > schema/active_users.sql << 'EOF'
-- require: users.sql
CREATE VIEW active_users AS
SELECT * FROM users;
EOF

Notice the -- require: users.sql? This is like import in Python or require in Node.js - it declares dependencies and lets you organize your schema however makes sense for your project.

Apply the schema to your dev database:

pgmt apply

pgmt will:

  • Create a shadow database
  • Load your schema files in dependency order
  • Compare with your dev database
  • Apply the necessary changes

Here’s what makes pgmt special. Let’s add a column to the base table:

cat > schema/users.sql << 'EOF'
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL,
full_name TEXT NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT true, -- New column
created_at TIMESTAMP DEFAULT NOW()
);
EOF

Now apply the change:

pgmt apply

What just happened?

pgmt automatically:

  1. Dropped the active_users view (its column list is about to change)
  2. Added the column: ALTER TABLE users ADD COLUMN is_active boolean DEFAULT true NOT NULL
  3. Recreated the active_users view, which now includes is_active

The view file didn’t change - it still says SELECT * FROM users - but pgmt knows it needs to be recreated because the underlying table structure changed.

If you rename full_name to name in the schema file, pgmt sees a column named full_name that disappeared and a column named name that appeared. From the schema files alone there’s no way to tell a rename apart from a genuine drop-and-add, and guessing wrong in either direction loses data - so pgmt refuses to guess. It proposes the drop and flags it as a destructive operation for you to review.

To actually rename a column:

  • In development: run the rename yourself against the dev database (psql -c 'ALTER TABLE users RENAME COLUMN full_name TO name'), then update the schema file to match. The next pgmt apply sees no difference.
  • For production: run pgmt migrate new and edit the generated migration, replacing the DROP COLUMN/ADD COLUMN pair with a single RENAME COLUMN statement. pgmt migrate validate replays the edited migration against a shadow database and confirms your rename produces the declared schema.

This review-don’t-guess approach is a core design decision - see the philosophy page for the reasoning.

Let’s add another view that depends on the first one:

cat > schema/recent_users.sql << 'EOF'
-- require: active_users.sql
CREATE VIEW recent_users AS
SELECT * FROM active_users
WHERE created_at > NOW() - INTERVAL '7 days';
EOF

Apply it:

pgmt apply

Now update the active_users view definition to add a filter:

cat > schema/active_users.sql << 'EOF'
-- require: users.sql
CREATE VIEW active_users AS
SELECT * FROM users WHERE email IS NOT NULL; -- Added filter
EOF

Apply again:

pgmt apply

pgmt automatically:

  1. Dropped recent_users (depends on active_users)
  2. Dropped active_users (the view we’re changing)
  3. Created active_users (new definition with filter)
  4. Created recent_users (unchanged, but depends on active_users)

All in the correct order, based on the dependency graph.

With traditional migration tools, you’d manually write:

-- Hope you remember all the dependencies!
DROP VIEW recent_users;
DROP VIEW active_users;
CREATE VIEW active_users AS ...; -- Better get the order right
CREATE VIEW recent_users AS ...;

With pgmt, you edit views and functions like source code. pgmt handles the drop/recreate mechanics, figures out the dependency cascade, and applies changes in the correct order. No manual migration scripts needed.

The -- require: statements aren’t just for dependency resolution - they’re for organizing your schema like a real programming language:

schema/
├── 01_foundation/
└── extensions.sql # PostgreSQL extensions
├── 02_core/
├── users.sql # Core entities
└── posts.sql # require: users.sql
└── 03_features/
└── analytics.sql # require: users.sql, posts.sql

Organize by domain, by feature, by team ownership - whatever makes sense for YOUR project. pgmt handles the dependency graph automatically.

When you’re ready to deploy to production, generate an explicit migration:

pgmt migrate new "initial schema with user views"

This creates a migration file like:

migrations/1734567890_initial_schema_with_user_views.sql

Review the generated SQL:

cat migrations/*_initial_schema_with_user_views.sql

You’ll see explicit SQL statements that you can review, test, and version control before deploying to production.

pgmt apply --watch

Automatically applies safe changes as you edit schema files. Prompts for confirmation on destructive operations (like dropping columns).

pgmt apply --dry-run

See what would change without actually applying it.

pgmt migrate status

See which migrations have been applied to your database.

If you use Claude Code or another agent compatible with the skills.sh ecosystem, install the pgmt skill so the agent knows how to edit schema files and generate migrations correctly instead of hand-writing migration SQL:

npx skills add gdpotter/pgmt

Organize your schema:

Work with a team:

Handle complex changes: