CLI Reference
Complete command-line reference for go-migration — all commands, flags, and usage examples.
CLI Reference
go-migration includes a CLI tool for scaffolding migration files, seeder files, and factory files directly from the terminal. This page documents every available command and flag.
The global go-migration CLI handles file scaffolding (make:migration, make:seeder, make:factory) and the migrations tracking table. To actually run migrations, seed data, or use factories, you need a project-specific binary. See Building Your CLI for details.
Installation
Install the CLI tool:
go install github.com/gopackx/go-migration/cmd/migrator@latestgo install names the binary after the last path element, so this installs a binary called migrator into your $GOBIN. The CLI's own help and usage text refers to itself as go-migration, so if you prefer that name, build it explicitly with -o:
go build -o go-migration github.com/gopackx/go-migration/cmd/migratorVerify the installation:
go-migration --helpIf you installed via go install, the binary is named migrator. Either run the commands on this page as migrator <command> or rename/build it to go-migration as shown above. The examples below use go-migration.
Command Reference
| Command | Description |
|---|---|
init | Scaffold a new project with directories, config file, and CLI entry point |
migrate | Run all pending migrations (--dry-run) |
migrate:rollback | Roll back the last batch of migrations (--step N, --dry-run) |
migrate:reset | Roll back all migrations (--force, --dry-run) |
migrate:refresh | Roll back all migrations then re-run them (--dry-run) |
migrate:fresh | Drop all tables then run all migrations (--force, --dry-run) |
migrate:status | Show the status of each migration; read-only (--check exits 1 if any migration is pending) |
migrate:install | Create the migrations tracking table (under the migration lock) |
make:migration | Generate a new migration file (--create or --table); name must be lower snake_case |
make:seeder | Generate a new seeder file; name must be lower snake_case |
make:factory | Generate a new factory file; name must be lower snake_case |
db:seed | Run database seeders (--class, --tag) |
db:seed:rollback | Roll back a specific seeder (--class) (new in v1.0.0) |
db:seed:truncate | Truncate (delete all rows from) a specific table (--table) (new in v1.0.0) |
The commands that change the database — migrate, migrate:rollback, migrate:reset, migrate:refresh, migrate:fresh, migrate:install, db:seed, db:seed:rollback, and db:seed:truncate — also accept --lock-timeout and --no-lock. See Migration Lock Flags.
The CLI has no third-party CLI dependency: commands are dispatched by a small hand-written router in pkg/cli on top of the standard library flag package. The entry point is app.Run() in github.com/gopackx/go-migration/pkg/cli/app.
Smart Command Classification
Commands like help, init, and all make:* commands skip database setup entirely. This means they work even without a valid database connection or config file — useful when scaffolding files on a fresh project.
Migration Lock Flags
Every command that writes to the database takes a cross-process migration lock before it touches the tracking table, so several processes (Kubernetes pods, app replicas, CI jobs) can run the same command at once: one does the work, the others wait and then find nothing left to do. Two flags control the lock:
| Flag | Type | Default | Description |
|---|---|---|---|
--lock-timeout | duration | 30s | How long to wait for a lock held by another process (e.g. 30s, 2m). 0 means a single attempt. |
--no-lock | bool | false | Do not take the cross-process migration lock |
# Wait up to two minutes for another process to finish migrating
go-migration migrate --lock-timeout=2m
# Single-process setups, or a pool limited to one connection
go-migration migrate --no-lockIf the lock is not acquired in time, the command fails with could not acquire migration lock within 30s ... (migrator.ErrLockTimeout) without changing anything. On PostgreSQL and MySQL the lock is held on its own connection, so the pool must allow at least two open connections (max_open_conns of 0 or at least 2); a pool limited to one connection is rejected with an error suggesting a larger pool or --no-lock. SQLite takes no lock. migrate:status never takes the lock. See Concurrency and Locking for the details.
Exit Codes
| Exit code | Meaning |
|---|---|
0 | The command succeeded (for migrate:status --check: nothing is pending) |
1 | The command failed — the error is printed to stderr as Error: ... — or migrate:status --check found pending migrations, in which case stderr shows N migration(s) pending |
Global Flags
The --config flag is available on all commands:
go-migration migrate --config=./config/migration.json| Flag | Type | Default | Description |
|---|---|---|---|
--config | string | migration.json | Path to the configuration file |
If you are still using a YAML configuration file, you can point to it explicitly via the --config flag:
go-migration migrate --config=migration.yamlNote that YAML files are still supported but deprecated — go-migration prints a deprecation warning to stderr when it loads a .yaml/.yml config. Migrate to JSON (migration.json) instead.
init
Scaffold a new go-migration project with the recommended directory structure, configuration file, and CLI entry point. This is the fastest way to get started with a new project.
go-migration init [--module=MODULE] [--force]The init command reads your go.mod file to auto-detect the module path. This module path is used in the generated cmd/migrator/main.go for blank imports. If go.mod is not found or you want to use a different path, use the --module flag to override it.
What Gets Created
Directories:
| Directory | Purpose |
|---|---|
database/migrations/ | Migration files |
database/seeders/ | Seeder files |
database/factories/ | Factory files |
cmd/migrator/ | CLI entry point |
Files:
| File | Purpose |
|---|---|
cmd/migrator/main.go | Entry point that calls app.Run() with blank imports for migrations, seeders, and the database drivers |
migration.json | Default configuration file in JSON format with placeholder environment variables |
The generated cmd/migrator/main.go contains a main() function that calls app.Run(), along with blank imports (_) for your database/migrations and database/seeders packages and the three database drivers. This is the same pattern described in Building Your CLI, but generated automatically:
package main
import (
"github.com/gopackx/go-migration/pkg/cli/app"
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
_ "modernc.org/sqlite"
_ "your-module/database/migrations"
_ "your-module/database/seeders"
)
func main() {
app.Run()
}Projects generated before v1.2.0 call the removed Run() function of pkg/migrator and blank-import the old CGO SQLite driver. Update them as shown above: import github.com/gopackx/go-migration/pkg/cli/app, call app.Run(), and blank-import modernc.org/sqlite (a pure-Go driver, so the binary builds with CGO_ENABLED=0).
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--module | string | (auto-detected from go.mod) | Override the Go module path used in generated import statements |
--force | bool | false | Overwrite existing files instead of skipping them |
Default Behavior vs --force
By default, init skips any files or directories that already exist and prints a warning for each skipped item. This makes it safe to run on an existing project without losing any work.
With --force, existing files are overwritten. Use this if you want to regenerate the scaffolding from scratch.
Using --force will overwrite existing files like cmd/migrator/main.go and migration.json. Make sure you don't have unsaved changes in those files before running with --force.
Example Output
$ go-migration init
Creating project structure...
Created directories:
✓ database/migrations/
✓ database/seeders/
✓ database/factories/
✓ cmd/migrator/
Created files:
✓ cmd/migrator/main.go
✓ migration.json
Project initialized successfully!
Run 'go-migration make:migration create_users_table' to create your first migration.Examples
# Initialize a new project (auto-detects module path from go.mod)
go-migration init
# Initialize with a custom module path
go-migration init --module=github.com/myuser/myproject
# Re-initialize and overwrite existing files
go-migration init --forceinit is a smart command — it does not require a database connection or config file to run. You can use it on a completely fresh project.
migrate
Run all pending migrations that have not yet been applied to the database.
go-migration migrate [--dry-run] [--lock-timeout=DURATION] [--no-lock]Migrations are executed in the order they were registered. Each run is assigned a batch number so you can roll back a group of migrations together.
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--dry-run | bool | false | Print the SQL that would run without executing it against the database |
--lock-timeout | duration | 30s | How long to wait for the migration lock |
--no-lock | bool | false | Do not take the cross-process migration lock |
--dry-run never writes to the database — not even the tracking table — but it still reads the tracking table to find pending migrations, so the table must already exist. Run migrate:install once first; otherwise dry-run fails with ErrTrackingTable. This applies to every command that accepts --dry-run.
Examples
# Run all pending migrations
go-migration migrate
# Preview the SQL without applying it
go-migration migrate --dry-run
# Run with a specific config file
go-migration migrate --config=./config/production.jsonIf there are no pending migrations, the command exits with a message indicating nothing to migrate.
migrate:rollback
Roll back the most recent batch of migrations. Use the --step flag to roll back a specific number of migrations.
go-migration migrate:rollback [--step=N] [--dry-run] [--lock-timeout=DURATION] [--no-lock]Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--step | int | 0 | Number of migrations to roll back. 0 rolls back the entire last batch. |
--dry-run | bool | false | Print the SQL that would run without executing it against the database |
--lock-timeout | duration | 30s | How long to wait for the migration lock |
--no-lock | bool | false | Do not take the cross-process migration lock |
Examples
# Roll back the last batch
go-migration migrate:rollback
# Roll back the last 3 migrations
go-migration migrate:rollback --step=3Make sure all migrations have proper Down methods before rolling back. A missing or incomplete Down method will cause the rollback to fail.
migrate:reset
Roll back all migrations in reverse order, returning the database to a clean state.
go-migration migrate:reset [--force] [--dry-run] [--lock-timeout=DURATION] [--no-lock]This calls the Down method on every applied migration, starting from the most recently applied. Before-migrate hooks run for each migration, as they do for migrate and migrate:rollback.
migrate:reset prompts for confirmation before proceeding. Pass --force to skip the confirmation prompt (useful for CI/CD pipelines).
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--force | bool | false | Skip the confirmation prompt |
--dry-run | bool | false | Print the SQL that would run without executing it against the database |
--lock-timeout | duration | 30s | How long to wait for the migration lock |
--no-lock | bool | false | Do not take the cross-process migration lock |
Examples
# Roll back all migrations (will prompt for confirmation)
go-migration migrate:reset
# Roll back all migrations without confirmation
go-migration migrate:reset --force
# Roll back all migrations with a specific config
go-migration migrate:reset --config=./config/staging.jsonmigrate:refresh
Roll back all migrations and then re-run them. This is equivalent to running migrate:reset followed by migrate.
go-migration migrate:refresh [--dry-run] [--lock-timeout=DURATION] [--no-lock]Useful during development when you want to rebuild the entire database schema from scratch while keeping your migration files intact. One migration lock is held across both phases, so no other process can run in between.
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--dry-run | bool | false | Print the SQL that would run without executing it against the database |
--lock-timeout | duration | 30s | How long to wait for the migration lock |
--no-lock | bool | false | Do not take the cross-process migration lock |
Examples
# Refresh all migrations
go-migration migrate:refresh
# Preview the SQL without applying it
go-migration migrate:refresh --dry-run
# Refresh with a specific config
go-migration migrate:refresh --config=./config/development.jsonmigrate:fresh
Drop all tables from the database, then run all migrations from the beginning.
go-migration migrate:fresh [--force] [--dry-run] [--lock-timeout=DURATION] [--no-lock]Unlike migrate:refresh, this command does not call Down methods. It drops every table directly, then runs all Up methods in batch 1. It works on every supported database: PostgreSQL drops and recreates the public schema; MySQL and SQLite list the tables and drop each one with foreign key checks turned off. The lock is taken before anything is dropped. With --dry-run the DROP statements are printed and every migration is shown as pending in batch 1. See Fresh for the per-database details.
migrate:fresh prompts for confirmation before proceeding. Pass --force to skip the confirmation prompt (useful for CI/CD pipelines).
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--force | bool | false | Skip the confirmation prompt |
--dry-run | bool | false | Print the SQL that would run without executing it against the database |
--lock-timeout | duration | 30s | How long to wait for the migration lock |
--no-lock | bool | false | Do not take the cross-process migration lock |
Examples
# Drop all tables and re-migrate (will prompt for confirmation)
go-migration migrate:fresh
# Drop all tables without confirmation
go-migration migrate:fresh --force
# Preview the SQL without applying it
go-migration migrate:fresh --dry-run
# Fresh migration with a specific config
go-migration migrate:fresh --config=./config/test.jsonmigrate:fresh drops all tables without running Down methods. This will destroy all data. Never use this in production.
migrate:status
Display the current status of all registered migrations, showing which have been run and which are pending.
go-migration migrate:status [--check]migrate:status is read-only: it never creates the tracking table and does not wait for the migration lock. If the tracking table does not exist yet, every registered migration is reported as pending.
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--check | bool | false | After printing the table, exit with status 1 if any migration is pending (prints N migration(s) pending to stderr) |
Example Output
The output is a simple tab-aligned table with Migration, Status, Batch, and Applied At columns. Applied migrations show Applied along with their batch number and timestamp; pending ones show Pending with the remaining columns blank.
Migration Status Batch Applied At
20240101_000001_create_users_table Applied 1 2024-01-01 09:30:00
20240115_000001_create_posts_table Applied 1 2024-01-01 09:30:01
20240201_000001_add_phone_to_users Applied 2 2024-02-01 14:12:45
20240301_000001_create_comments_table PendingExamples
# Show migration status
go-migration migrate:status
# Show status using a specific config
go-migration migrate:status --config=./config/production.json
# Fail (exit 1) if any migration is pending, e.g. in CI
go-migration migrate:status --check
# Wait until another process (e.g. a migration Job) has applied everything
until go-migration migrate:status --check; do sleep 5; done--check is new in v1.2.0. It makes migrate:status usable as a Kubernetes initContainer or CI step that waits for, or verifies, the migration run. See Concurrency and Locking.
migrate:install
Create the migrations tracking table in the database. This table stores which migrations have been run and their batch numbers.
go-migration migrate:install [--lock-timeout=DURATION] [--no-lock]The table is created while holding the migration lock, so concurrent installs and migrations do not race on CREATE TABLE IF NOT EXISTS. Because of that, on PostgreSQL and MySQL it needs a pool of at least two connections (or --no-lock), like the other writing commands.
Flags
| Flag | Type | Default | Description |
|---|---|---|---|
--lock-timeout | duration | 30s | How long to wait for the migration lock |
--no-lock | bool | false | Do not take the cross-process migration lock |
The migrate command creates the tracking table automatically if it doesn't exist. You do need migrate:install before the first --dry-run, because dry-run reads the tracking table but never creates it.
Examples
# Create the migrations table
go-migration migrate:install
# Create with a specific config
go-migration migrate:install --config=./config/migration.jsonmake:migration
Generate a new migration file with boilerplate code. Use --create for new table migrations or --table for table modification migrations.
go-migration make:migration <name> [--create=TABLE] [--table=TABLE]The name must be lower snake_case — it has to match ^[a-z][a-z0-9_]*$, for example create_orders_table. The generator derives the timestamped file name (YYYY_MM_DD_HHMMSS_RRRR_<name>.go) and the Go type name from it. Names such as CreateOrders or add-status are rejected with an error instead of producing a file whose init() would panic.
Flags
| Flag | Type | Description |
|---|---|---|
--create | string | Generate a migration that creates a new table with the given name |
--table | string | Generate a migration that modifies an existing table with the given name |
Examples
# Generate a blank migration
go-migration make:migration create_users_table
# Generate a migration to create a new "users" table
go-migration make:migration create_users_table --create=users
# Generate a migration to modify the "users" table
go-migration make:migration add_email_to_users --table=usersGenerated File: --create
When using --create=users, the generated file includes s.Create() scaffolding:
package migrations
import (
"github.com/gopackx/go-migration/pkg/migrator"
"github.com/gopackx/go-migration/pkg/schema"
)
// CreateUsersTable migration.
type CreateUsersTable struct{}
func init() {
migrator.AutoRegister("2024_01_01_090000_4821_create_users_table", &CreateUsersTable{})
}
// Up runs the migration.
func (m *CreateUsersTable) Up(s *schema.Builder) error {
return s.Create("users", func(bp *schema.Blueprint) {
bp.ID()
bp.Timestamps()
})
}
// Down reverses the migration.
func (m *CreateUsersTable) Down(s *schema.Builder) error {
return s.Drop("users")
}Generated File: --table
When using --table=users, the generated file includes s.Alter() scaffolding:
package migrations
import (
"github.com/gopackx/go-migration/pkg/migrator"
"github.com/gopackx/go-migration/pkg/schema"
)
// AddEmailToUsers migration.
type AddEmailToUsers struct{}
func init() {
migrator.AutoRegister("2024_02_01_141245_0937_add_email_to_users", &AddEmailToUsers{})
}
// Up runs the migration.
func (m *AddEmailToUsers) Up(s *schema.Builder) error {
return s.Alter("users", func(bp *schema.Blueprint) {
// TODO: define table alterations
})
}
// Down reverses the migration.
func (m *AddEmailToUsers) Down(s *schema.Builder) error {
return s.Alter("users", func(bp *schema.Blueprint) {
// TODO: reverse table alterations
})
}make:seeder
Generate a new seeder file with boilerplate code. The seeder name is passed as a positional argument and must be lower snake_case (^[a-z][a-z0-9_]*$); the file name (<name>_seeder.go) and the struct name are derived from it.
go-migration make:seeder <name>This command takes no flags beyond the global --config.
Examples
# Generate a seeder file
go-migration make:seeder user
# Generate a seeder for a specific model
go-migration make:seeder user_profileGenerated File
package seeders
import (
"database/sql"
"github.com/gopackx/go-migration/pkg/seeder"
)
func init() {
seeder.AutoRegister("User", &User{})
}
// User seeder.
type User struct{}
// Run executes the seeder.
func (s *User) Run(db *sql.DB) error {
// TODO: implement seeder
return nil
}make:factory
Generate a new factory file with boilerplate code for model factories with faker integration. The name must be lower snake_case (^[a-z][a-z0-9_]*$); the file name (<name>_factory.go) and the type name are derived from it.
go-migration make:factory <name>Examples
# Generate a factory file
go-migration make:factory users
# Generate a factory for a specific model
go-migration make:factory user_profileGenerated File
package factories
import (
"github.com/gopackx/go-migration/pkg/seeder/factory"
)
// Users model.
type Users struct {
// TODO: define model fields
}
// NewUsersFactory creates a factory for Users.
func NewUsersFactory() *factory.Factory[Users] {
return factory.NewFactory(func(f factory.Faker) Users {
return Users{
// TODO: set fields using faker
}
})
}db:seed
Run database seeders to populate the database with data. By default, all registered seeders are executed. Use --class to run a specific seeder, or --tag to run seeders matching a tag.
go-migration db:seed [--class=CLASS] [--tag=TAG] [--lock-timeout=DURATION] [--no-lock]Seeding runs while holding the migration lock, so seeders never run concurrently with each other or with migrations.
Flags
| Flag | Type | Description |
|---|---|---|
--class | string | Run only the specified seeder by name |
--tag | string | Run only seeders matching the given tag |
--lock-timeout | duration | How long to wait for the migration lock (default 30s) |
--no-lock | bool | Do not take the cross-process migration lock |
The --tag flag is new in v1.0.0. It runs only seeders that implement the TaggedSeeder interface and return the matching tag. See Seeder Tags for details.
Examples
# Run all seeders
go-migration db:seed
# Run a specific seeder
go-migration db:seed --class=UserSeeder
# Run seeders tagged as "development"
go-migration db:seed --tag=development
# Run with a specific config
go-migration db:seed --config=./config/development.jsonSeeders run in dependency order when using DependsOn(). See Seeder Dependencies for details.
db:seed:rollback
New in v1.0.0
Roll back a specific seeder by calling its Rollback method. The seeder must implement the RollbackableSeeder interface.
go-migration db:seed:rollback --class=CLASS [--lock-timeout=DURATION] [--no-lock]Flags
| Flag | Type | Description |
|---|---|---|
--class | string | (Required) The seeder name to roll back |
--lock-timeout | duration | How long to wait for the migration lock (default 30s) |
--no-lock | bool | Do not take the cross-process migration lock |
Examples
# Roll back a specific seeder
go-migration db:seed:rollback --class=UserSeeder
# Roll back with a specific config
go-migration db:seed:rollback --class=UserSeeder --config=./config/development.jsonThe seeder must implement the RollbackableSeeder interface. If it doesn't, the command returns an error. See Seeder Rollback for details.
db:seed:truncate
New in v1.0.0
Delete all rows from a specific table. This is useful for clearing seeded data without dropping the table itself.
go-migration db:seed:truncate --table=TABLE [--lock-timeout=DURATION] [--no-lock]Flags
| Flag | Type | Description |
|---|---|---|
--table | string | (Required) The table name to truncate |
--lock-timeout | duration | How long to wait for the migration lock (default 30s) |
--no-lock | bool | Do not take the cross-process migration lock |
Examples
# Truncate the users table
go-migration db:seed:truncate --table=users
# Truncate with a specific config
go-migration db:seed:truncate --table=posts --config=./config/development.jsonThis deletes all rows from the table using DELETE FROM. The table structure and schema remain intact. The table name is quoted for the configured driver (backticks on MySQL, double quotes on PostgreSQL and SQLite), so since v1.2.0 the command also works on MySQL.
Flags Reference
| Flag | Commands | Type | Description |
|---|---|---|---|
--step | migrate:rollback | int | Number of migrations to roll back |
--dry-run | migrate, migrate:rollback, migrate:reset, migrate:refresh, migrate:fresh | bool | Print the SQL without executing it (requires the tracking table; run migrate:install first) |
--lock-timeout | migrate, migrate:rollback, migrate:reset, migrate:refresh, migrate:fresh, migrate:install, db:seed, db:seed:rollback, db:seed:truncate | duration | How long to wait for the migration lock (default 30s) |
--no-lock | Same commands as --lock-timeout | bool | Do not take the cross-process migration lock |
--check | migrate:status | bool | Exit with status 1 if any migration is pending |
--module | init | string | Override the Go module path used in generated imports |
--force | init, migrate:fresh, migrate:reset | bool | Overwrite existing files (init) or skip confirmation prompt (migrate:fresh, migrate:reset) |
--create | make:migration | string | Table name for a create-table migration |
--table | make:migration | string | Table name for a table-modification migration |
--table | db:seed:truncate | string | Table name to truncate (required) |
--class | db:seed | string | Run only the specified seeder by name |
--class | db:seed:rollback | string | Seeder name to roll back (required) |
--tag | db:seed | string | Run seeders matching the given tag |
| (positional) | make:migration, make:seeder, make:factory | string | Migration/seeder/factory name (required, lower snake_case) |
--config | All commands | string | Path to the configuration file |
What's Next?
- Configuration — set up database connections with JSON (or deprecated YAML) and environment variables
- Getting Started — install go-migration and run your first migration
- Migrations — learn how to define and manage migrations