A thread-safe, lightweight, and flexible database migration tool for PostgreSQL.
pgroll runs your plain-SQL migrations through the postgres
(PostgresJS) client. Migrations are applied inside a transaction and guarded by a session-level
advisory lock, so it is safe to run concurrently from multiple processes — only one migration runs
at a time.
Supported PostgreSQL clients
- PostgresJS
- node-postgres (
pg)
- up — Apply all pending migrations.
- down — Roll back all applied migrations (down to version
0). - go — Migrate forward or backward to a specific version (
go 0reverts everything). - create — Generate a matching up/down migration file pair.
- Node.js ≥ 24.16.0 (the package ships as ESM)
- A reachable PostgreSQL database
Install globally to use the CLI anywhere:
npm install -g pgroll…or add it to a project and run it with npx:
npm install pgrollThe CLI connects using a connection URL or the standard libpq environment variables (read by the
underlying postgres client).
Option 1 — connection URL via the -u, --url flag:
npx pgroll --url "postgres://user:password@localhost:5432/mydb" upOption 2 — environment variables:
| Variable | Description | Default |
|---|---|---|
PGHOST |
Server host | localhost |
PGPORT |
Server port | 5432 |
PGDATABASE |
Database name | — |
PGUSER |
User name | OS user name |
PGPASSWORD |
Password | — |
PGHOST=localhost PGDATABASE=mydb PGUSER=me PGPASSWORD=secret npx pgroll upWhen --url is provided, it takes precedence over the PG* variables.
Migrations live in a directory (default ./migrations, override with -d, --migrationDir <path>).
Each migration is a pair of plain .sql files distinguished by suffix:
20240619121610402_init_up.sql # applied on "up"
20240619121610402_init_down.sql # applied on "down"
- The leading number is a timestamp generated by
create; it determines order. upmigrations are applied in ascending filename order;downmigrations in descending order, so rollbacks unwind in the reverse of how they were applied.- A migration's version is its position in that ordered list (the first up migration is version
1).
pgroll [global options] <command>
| Option | Description |
|---|---|
-d, --migrationDir <path> |
Directory holding the migration files (default ./migrations). |
-u, --url <url> |
PostgreSQL connection URL (overrides PG* env vars). |
-V, --version |
Print the pgroll version. |
-h, --help |
Show help. |
Apply all pending migrations:
npx pgroll upRoll back every applied migration:
npx pgroll downMigrate to a specific version (moves up or down as needed; 0 rolls everything back):
npx pgroll go <version>Create a new migration pair (writes <timestamp>_<name>_up.sql and ..._down.sql with
placeholder contents for you to fill in):
npx pgroll create <name>A typical workflow:
npx pgroll create add_users_table # creates the up/down file pair
# …edit the generated *_up.sql / *_down.sql files…
npx pgroll up # apply itpgroll can also be used as a library. Pass it a postgres client instance and drive migrations
directly:
import postgres from 'postgres';
import { Migrator } from 'pgroll';
const sql = postgres('postgres://user:password@localhost:5432/mydb');
const migrator = new Migrator(sql, './migrations');
// Apply all pending migrations
await migrator.up();
console.log('Current version:', await migrator.getCurrentVersion());
// Migrate to a specific version, logging progress as it goes
await migrator.go(0, { eventHandler: info => console.log(info) });
await sql.end();| Parameter | Type | Description |
|---|---|---|
dbClient |
Sql (PostgresJS) |
A postgres client instance. |
migrationsDir |
string (optional) |
Directory of migration files. Defaults to <cwd>/migrations. |
| Method | Description |
|---|---|
up(opts?) |
Apply all pending up migrations. |
down(opts?) |
Roll back all applied migrations (to version 0). |
go(version, opts?) |
Migrate forward or backward to version (0 reverts everything). |
getCurrentVersion() |
Resolve to the highest applied version (0 if none have been applied). |
opts is { eventHandler: (info: string) => void } — an optional callback invoked with a
human-readable message as each migration is applied.
On the first run, pgroll creates a bookkeeping table:
CREATE TABLE IF NOT EXISTS migrations (
name varchar(500) PRIMARY KEY,
version smallint NOT NULL,
applied_at timestamp DEFAULT CURRENT_TIMESTAMP
);Each up/down/go run reserves a single connection, takes a session-level
pg_advisory_lock, and applies the relevant migration files within a transaction — recording or
removing the corresponding rows in migrations as it goes. The advisory lock serializes concurrent
runs across processes, and the surrounding transaction means a failed migration rolls back cleanly.