dblift migrate
Apply migrations. Runs every pending migration in version order and records each one in the schema history table.
Synopsis
$ dblift migrate [--target-version VERSION] [--validate-only]
[--tags TAGS] [--exclude-tags TAGS]
[--versions LIST] [--exclude-versions LIST]
[--placeholders K=V [K=V ...]]
[--strict] [--table TABLE]Options
--tableCustom schema history table name (default: dblift_schema_history)--strictEnable strict mode - fail if any previously applied migration is missing and require migrations to be applied in strict version order--tagsExecute migrations with specified tags (comma-separated list)--exclude-tagsSkip migrations with specified tags (comma-separated list)--versionsExecute only specific versions (comma-separated list)--exclude-versionsSkip specific versions (comma-separated list)--placeholdersSQL placeholders for variable substitution in migration scripts. Format: key1=value1,key2=value2 or key1=value1 key2=value2. Can be repeated: --placeholders k1=v1 --placeholders k2=v2--db-urlDatabase URL--db-usernameDatabase username--db-passwordDatabase password--db-schemaDatabase schema--target-versionTarget version to migrate to--validate-onlyOnly validate migrations without applying them--mark-as-executedMark migrations as executed without running them (for migration applied outside dblift)--show-sqlShow SQL statements in command output and reports--show-query-resultsShow rows returned by SELECT statements in command output and reports--formatOutput format (default: console)Filters
By default migrate applies every pending versioned file, then any repeatable whose checksum changed. Narrow that set with tags (from the filename), an explicit version list, or a ceiling version. Matching is case-sensitive. Callbacks (`beforeMigrate__*.sql` and the rest) still run around the selected scripts.
Preview only files tagged auth — V1_2_0__add_note[auth].sql in this tree. core files stay pending.
$ dblift migrate --tags=auth --dry-run --show-sql┏━━━━━━━━━━━━━━━━━━━━━━━ DBLIFT DATABASE MIGRATION LOG ━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Timestamp: 2026-01-15 12:00:00 ┃
┃ Dblift version: 3.9.0 ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ DBLIFT COMMAND: MIGRATE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Connected to database ./app.db (SQLite) ┃
┃ Database: ./app.db ┃
┃ Schema: main ┃
┃ Schema Version: 1.0.0 ┃
┃ Database URL: sqlite:///./app.db ┃
┃ Filtering Options: --dry-run --tags=auth --show-sql ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
Found 1 pending migration(s)
WARNING: Placeholder '${TABLE_NAME}' not found, leaving as is
WARNING: Placeholder '${LABEL_VALUE}' not found, leaving as is
DRY RUN: Would execute the following migrations:
- V1_2_0__add_note[auth].sql
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SUCCESS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ - Dry run — 1 migration(s) would be applied ┃
┃ Command MIGRATE completed successfully (Execution time: 12 ms) ┃
┃ Schema Version: 1.0.0 ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
SQL Statements:
--------------------------------------------------------------------------------
-- V1_2_0__add_note[auth].sql
ALTER TABLE ${TABLE_NAME} ADD COLUMN note TEXT DEFAULT '${LABEL_VALUE}';
--------------------------------------------------------------------------------
Preview a single version. Combine with --exclude-versions or --exclude-tags to skip others.
$ dblift migrate --versions 1.1.0 --dry-run --show-sql┏━━━━━━━━━━━━━━━━━━━━━━━ DBLIFT DATABASE MIGRATION LOG ━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Timestamp: 2026-01-15 12:00:00 ┃ ┃ Dblift version: 3.9.0 ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ DBLIFT COMMAND: MIGRATE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Connected to database ./app.db (SQLite) ┃ ┃ Database: ./app.db ┃ ┃ Schema: main ┃ ┃ Schema Version: 1.0.0 ┃ ┃ Database URL: sqlite:///./app.db ┃ ┃ Filtering Options: --dry-run --versions=1.1.0 --show-sql ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Found 1 callback migrations. Found 1 pending migration(s) DRY RUN: Would execute the following migrations: - V1_1_0__users_created_at[core].sql ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SUCCESS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ - Dry run — 1 migration(s) would be applied ┃ ┃ Command MIGRATE completed successfully (Execution time: 12 ms) ┃ ┃ Schema Version: 1.0.0 ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ SQL Statements: -------------------------------------------------------------------------------- -- V1_1_0__users_created_at[core].sql ALTER TABLE users ADD COLUMN created_at TEXT; --------------------------------------------------------------------------------
Placeholders
SQL files may contain `${NAME}` or `${NAME:default}`. Values come from config `placeholders:`, then `--placeholders`. Two spellings are accepted: a comma-separated string, or repeated `KEY=value` tokens. Unresolved tokens are left as-is and a warning is logged. Python migrations read `context.placeholders` — they are not substituted automatically.
Substitute TABLE_NAME and LABEL_VALUE before the statements run.
$ dblift migrate --placeholders TABLE_NAME=users LABEL_VALUE=hello --dry-run --show-sql┏━━━━━━━━━━━━━━━━━━━━━━━ DBLIFT DATABASE MIGRATION LOG ━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Timestamp: 2026-01-15 12:00:00 ┃ ┃ Dblift version: 3.9.0 ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ DBLIFT COMMAND: MIGRATE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Connected to database ./app.db (SQLite) ┃ ┃ Database: ./app.db ┃ ┃ Schema: main ┃ ┃ Schema Version: 1.0.0 ┃ ┃ Database URL: sqlite:///./app.db ┃ ┃ Filtering Options: --dry-run --show-sql ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Found 1 callback migrations. Found 2 pending migration(s) DRY RUN: Would execute the following migrations: - V1_1_0__users_created_at[core].sql - V1_2_0__add_note[auth].sql ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SUCCESS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ - Dry run — 2 migration(s) would be applied ┃ ┃ Command MIGRATE completed successfully (Execution time: 12 ms) ┃ ┃ Schema Version: 1.0.0 ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ SQL Statements: -------------------------------------------------------------------------------- -- V1_1_0__users_created_at[core].sql ALTER TABLE users ADD COLUMN created_at TEXT; -- V1_2_0__add_note[auth].sql ALTER TABLE users ADD COLUMN note TEXT DEFAULT 'hello'; --------------------------------------------------------------------------------
Callbacks
A file named `{event}__{description}.sql` is not a migration. It runs at that point in the lifecycle — `beforeMigrate`, `afterEachMigrate`, `afterMigrateError`, and the others listed under Naming conventions. Put session setup there (lock timeouts, `PRAGMA`, `SET search_path`). Tags on a callback filename work the same way as on a versioned file.
Dry run and --show-sql
`--dry-run` applies nothing and does not create the schema-history table. `--show-sql` prints the statements that would run. With `--format json`, and only when `--show-sql` is set, the payload adds `show_sql: true` and a `sql` array of pending scripts, each with `script`, `version`, `description`, and `statements`. Placeholders that resolve are substituted, so a secret used as a placeholder value appears in that SQL. A placeholder with no value stays visible as `${VAR}`. The MCP `migrate_dry_run` tool takes `show_sql` (default false) and returns the same shape. It needs a live connection; `dblift mcp --offline` refuses the call. The tool never applies.
Global flags apply to every command: --config, --env, --scripts, --dry-run, --quiet, --log-level. See global flags.
Examples
Preview the exact SQL before it runs
$ dblift migrate --dry-run --show-sql┏━━━━━━━━━━━━━━━━━━━━━━━ DBLIFT DATABASE MIGRATION LOG ━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Timestamp: 2026-01-15 12:00:00 ┃
┃ Dblift version: 3.9.0 ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ DBLIFT COMMAND: MIGRATE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Connected to database ./app.db (SQLite) ┃
┃ Database: ./app.db ┃
┃ Schema: main ┃
┃ Schema Version: <none> ┃
┃ Database URL: sqlite:///./app.db ┃
┃ Filtering Options: --dry-run --show-sql ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
Found 1 callback migrations.
Found 1 pending migration(s)
DRY RUN: Would execute the following migrations:
- V1_0_0__create_users[core].sql
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SUCCESS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ - Dry run — 1 migration(s) would be applied ┃
┃ Command MIGRATE completed successfully (Execution time: 12 ms) ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
SQL Statements:
--------------------------------------------------------------------------------
-- V1_0_0__create_users[core].sql
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE
);
--------------------------------------------------------------------------------
Apply every pending file
$ dblift migrate┏━━━━━━━━━━━━━━━━━━━━━━━ DBLIFT DATABASE MIGRATION LOG ━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Timestamp: 2026-01-15 12:00:00 ┃ ┃ Dblift version: 3.9.0 ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ DBLIFT COMMAND: MIGRATE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Connected to database ./app.db (SQLite) ┃ ┃ Database: ./app.db ┃ ┃ Schema: main ┃ ┃ Schema Version: <none> ┃ ┃ Database URL: sqlite:///./app.db ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ Found 1 callback migrations. Found 1 pending migration(s) Migration lock acquired successfully Executing 1 beforeMigrate callback(s) Executing callback: beforeMigrate__set_pragma.sql Statement executed successfully, 0 rows affected Callback beforeMigrate__set_pragma.sql executed successfully ⠋ Migrating 0/1 0:00:00 Statement executed successfully ⠋ Migrating 0/1 0:00:00 Migration V1_0_0__create_users[core].sql executed successfully in 0ms ⠋ Migrating 0/1 0:00:00 Successfully applied migration V1_0_0__create_users[core].sql ⠋ Migrating 0/1 0:00:00 V1_0_0__create_users.sql ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1/1 0:00:00 ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ SUCCESS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ Command MIGRATE completed successfully (Execution time: 12 ms) ┃ ┃ Schema Version: 1.0.0 ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
Exit codes
0Command completed successfully4A paid-edition feature was invoked without a valid licence (EXIT_LICENSE_REQUIRED)