migratr 0.1.2

Every command. Every flag. Every exit code. Output below is pasted from runs of the released migratr binary; the runs used a scratch directory and a throwaway database.

migratr [--db PATH] [--dir PATH] [--json] <new|up|down|status|plan|restore> ...

Global flags

FlagDefaultDescription
--db <PATH>noneThe SQLite database file. Required by every command except new. Without it the command fails with usage.
--dir <PATH>migrationsThe migrations directory.
--jsonoffWrite one JSON document describing the outcome to stdout.
-h, --helpPrint help.
-V, --versionPrint the version.

The flags are global: they may come before or after the subcommand.

Commands

CommandWhat it does
new <NAME> [SPECS...]Scaffold a migration file.
up [--to V]Apply pending migrations.
down [--steps N]Reverse the newest N applied migrations (default 1).
statusList each migration as applied or pending.
plan [up [--to V] | down [--steps N]]Print the statements a run would execute. Changes nothing.
restore [--snapshot PATH] --yesReplace the database with a snapshot.

After every successful up and down, migratr writes schema.json in the migrations directory.

new

migratr new <NAME> [SPECS...]

Writes <YYYYMMDDHHMMSS>_<NAME>.json into --dir, creating the directory if needed. The name picks the operation, Rails style. Column specs are name[:type][:modifier...]; the modifiers are pk, notnull, unique and default=<expr>.

NameOperationSpecs
create_<table>create_tableRequired: at least one column.
add_<column>_to_<table>add_columnThe column.
drop_<table or index>drop_table or drop_indexNone. The definition is copied from schema.json.
remove_<column>_from_<table>drop_columnNone. The definition is copied from schema.json.
anything elsean empty upNone.
$ migratr new create_users id:integer:pk email:text:notnull:unique
created migrations/20261007235008_create_users.json
[exit 0]
$ migratr new create_posts id:integer:pk title:text:notnull --json
{"command":"new","path":"migrations/20261009000001_create_posts.json","status":"ok"}
[exit 0]

A drop of something schema.json does not describe fails with unknown_object. schema.json is written by a successful up or down, so run one first.

$ migratr new drop_ghosts --json
{"code":"unknown_object","command":"new","message":"table or index ghosts is not in schema.json","status":"error"}
[exit 1]

up

migratr --db PATH up [--to V]
FlagDefaultDescription
--to <V>all pendingStop after this version.

Applies each pending migration in version order. A migration and its ledger row go in one atomic run; a failure rolls it back. up refuses to run when an applied file was edited or a ledger row has no file.

$ migratr --db app.db up
applied 2 migration(s): 20261007235008, 20261007235009
[exit 0]
$ migratr --db app.db up --json
{"applied":[20261007235010],"command":"up","status":"ok"}
[exit 0]

down

migratr --db PATH down [--steps N]
FlagDefaultDescription
--steps <N>1Reverse the newest N applied migrations, newest first.

down reverses structure only. Data in dropped objects is not restored. If any operation in the range has no exact inverse, the whole range is refused before anything runs; see the rule for down.

$ migratr --db app.db down --steps 2
reverted 2 migration(s): 20261007235010, 20261007235009. Data in dropped objects is not restored. Latest snapshot: ./.migratr/snapshots/20261007235010_20261007235009_down.db
[exit 0]
$ migratr --db app.db down --json
{"command":"down","data_restored":false,"reverted":[20261007235008],"snapshot":"./.migratr/snapshots/20261007235010_20261007235008_down.db","status":"ok"}
[exit 0]

status

migratr --db PATH status

Lists each migration file as applied (with the time) or pending. An applied migration whose file was edited is marked CHECKSUM MISMATCH. A ledger row with no file is reported as missing. The last line names the latest snapshot.

$ migratr --db app.db status
20261007235008_create_users pending
20261007235009_add_name_to_users pending
latest snapshot: none
[exit 0]
$ migratr --db app.db status
20261007235008_create_users applied 2026-10-07T23:50:09.749Z
20261007235009_add_name_to_users applied 2026-10-07T23:50:17.955Z CHECKSUM MISMATCH
20261007235010_remove_name_from_users applied 2026-10-07T23:50:17.956Z
latest snapshot: ./.migratr/snapshots/20261007235017_20261007235010_up.db
[exit 0]
$ migratr --db app.db status --json
{"command":"status","latest_snapshot":null,"migrations":[{"applied_at":"2026-10-07T23:50:09.749Z","checksum_mismatch":false,"name":"create_users","state":"applied","version":20261007235008},{"applied_at":"2026-10-07T23:50:09.749Z","checksum_mismatch":false,"name":"add_name_to_users","state":"applied","version":20261007235009}],"missing":[],"status":"ok"}
[exit 0]

plan

migratr --db PATH plan [--to V]
migratr --db PATH plan up [--to V]
migratr --db PATH plan down [--steps N]

plan with no direction plans up. It prints the statements a run would execute, marks destructive steps, names the snapshot each would take, and reports the database size. It changes nothing: dry-run and run print the same statements.

$ migratr --db app.db plan down
plan down; database is 16384 bytes
20261007235009_add_name_to_users DESTRUCTIVE, snapshot 20261007235009_20261007235009_down.db
  ALTER TABLE "users" DROP COLUMN "name"
  DELETE FROM _migratr_migrations WHERE version = 20261007235009
[exit 0]
$ migratr --db app.db plan --json
{"command":"plan","database_bytes":16384,"direction":"up","status":"ok","steps":[]}
[exit 0]

restore

migratr --db PATH restore [--snapshot PATH] --yes
FlagDefaultDescription
--snapshot <PATH>the newestThe snapshot to restore.
--yesoffConfirm the restore. Without it nothing changes.

Restore acts on the local database file. Without --yes it fails with restore_not_confirmed and says what it would discard.

$ migratr --db app.db restore
error [restore_not_confirmed]: restore needs confirmation; it would discard everything written since the snapshot of 20261007235010 (ledger version 20261007235008); ledger versions removed: none
[exit 1]
$ migratr --db app.db restore --yes
restored; discards everything written since the snapshot of 20261007235010 (ledger version 20261007235008); ledger versions removed: none
[exit 0]
$ migratr --db app.db restore --yes --json
{"command":"restore","removed":[],"restored":true,"snapshot_time":"20261007235010","snapshot_version":20261007235008,"status":"ok"}
[exit 0]

JSON output

With --json, a command writes one JSON document to stdout and nothing else; logs go to stderr. The document carries command and status (ok or error). A success adds the outcome's fields. A failure adds code and message and the process exits non-zero.

CommandFields on success
newpath
upapplied (versions, in order)
downreverted (versions, newest first), data_restored, snapshot
statusmigrations (each with version, name, state, applied_at, checksum_mismatch), missing, latest_snapshot
plandirection, database_bytes, steps
restorerestored, removed, snapshot_time, snapshot_version

Exit codes and error codes

Exit 0 on success. A usage error exits 2. Every other failure exits 1. Error codes are stable across patch releases.

CodeExitCause
usage2Bad arguments, or a missing --db.
parse1A migration file or spec could not be parsed.
duplicate_version1Two migration files share a version.
io1A file or directory could not be read or written.
checksum_mismatch1An applied migration's file was edited.
missing_file1A ledger row has no migration file.
apply_failed1A migration failed and was rolled back.
irreversible1A migration in the down range has an operation with no inverse.
unparseable_table1A table's CREATE TABLE could not be parsed for a rebuild.
foreign_key_violation1A table rebuild left rows that violate foreign keys.
column_in_use1A dropped column is used by another object.
rebuild_not_first1An operation that needs a table rebuild is not first in its migration.
snapshot_failed1A snapshot could not be written, so the step did not run.
restore_not_confirmed1restore ran without --yes.
unknown_object1new named an object that is not in schema.json.
database1SQLite reported an error.

In text mode an error prints as error [<code>]: <message> on stderr.

$ migratr up
error [usage]: --db PATH is required
[exit 2]
$ migratr up --json
{"code":"usage","command":"up","message":"--db PATH is required","status":"error"}
[exit 2]
$ migratr --db app.db up --json
{"code":"checksum_mismatch","command":"up","message":"migration 20261007235009_add_name_to_users was edited after it was applied. Applied migrations are not edited: restore the file to what was applied, then write a new migration for the change","status":"error"}
[exit 1]
$ migratr --db app.db down --json
{"code":"irreversible","command":"down","message":"migration 20261008000000 cannot be reversed: operation 0 is raw SQL with no down","status":"error"}
[exit 1]
$ migratr --db app.db status --json
{"code":"parse","command":"status","message":"20261009000000_bad.json: at up[0]: unknown variant `nope`, expected one of `create_table`, `drop_table`, `rename_table`, `add_column`, `drop_column`, `rename_column`, `create_index`, `drop_index`, `raw_sql`","status":"error"}
[exit 1]
$ migratr --db nodir.db --dir nonexistent status --json
{"code":"io","command":"status","message":"nonexistent: No such file or directory (os error 2)","status":"error"}
[exit 1]

Errors raised by the argument parser itself, before migratr runs (an unknown subcommand, a flag missing its value), print the parser's message as plain text, exit 2, and are not JSON documents even with --json.

$ migratr bogus
error: unrecognized subcommand 'bogus'

Usage: migratr [OPTIONS] <COMMAND>

For more information, try '--help'.
[exit 2]

The [exit N] lines are added to the blocks above to show the process exit status; the binary does not print them. All blocks were captured from the 0.1.1 binary.