go-migrationGo Migration
CLI
Documentation

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:

bash
go install github.com/gopackx/go-migration/cmd/migrator@latest

go 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:

bash
go build -o go-migration github.com/gopackx/go-migration/cmd/migrator

Verify the installation:

bash
go-migration --help

If 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

CommandDescription
initScaffold a new project with directories, config file, and CLI entry point
migrateRun all pending migrations (--dry-run)
migrate:rollbackRoll back the last batch of migrations (--step N, --dry-run)
migrate:resetRoll back all migrations (--force, --dry-run)
migrate:refreshRoll back all migrations then re-run them (--dry-run)
migrate:freshDrop all tables then run all migrations (--force, --dry-run)
migrate:statusShow the status of each migration; read-only (--check exits 1 if any migration is pending)
migrate:installCreate the migrations tracking table (under the migration lock)
make:migrationGenerate a new migration file (--create or --table); name must be lower snake_case
make:seederGenerate a new seeder file; name must be lower snake_case
make:factoryGenerate a new factory file; name must be lower snake_case
db:seedRun database seeders (--class, --tag)
db:seed:rollbackRoll back a specific seeder (--class) (new in v1.0.0)
db:seed:truncateTruncate (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:

FlagTypeDefaultDescription
--lock-timeoutduration30sHow long to wait for a lock held by another process (e.g. 30s, 2m). 0 means a single attempt.
--no-lockboolfalseDo not take the cross-process migration lock
bash
# 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-lock

If 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 codeMeaning
0The command succeeded (for migrate:status --check: nothing is pending)
1The 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:

bash
go-migration migrate --config=./config/migration.json
FlagTypeDefaultDescription
--configstringmigration.jsonPath to the configuration file

If you are still using a YAML configuration file, you can point to it explicitly via the --config flag:

bash
go-migration migrate --config=migration.yaml

Note 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.

bash
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:

DirectoryPurpose
database/migrations/Migration files
database/seeders/Seeder files
database/factories/Factory files
cmd/migrator/CLI entry point

Files:

FilePurpose
cmd/migrator/main.goEntry point that calls app.Run() with blank imports for migrations, seeders, and the database drivers
migration.jsonDefault 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:

cmd/migrator/main.go
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

FlagTypeDefaultDescription
--modulestring(auto-detected from go.mod)Override the Go module path used in generated import statements
--forceboolfalseOverwrite 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

bash
# 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 --force

init 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.

bash
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

FlagTypeDefaultDescription
--dry-runboolfalsePrint the SQL that would run without executing it against the database
--lock-timeoutduration30sHow long to wait for the migration lock
--no-lockboolfalseDo 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

bash
# 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.json

If 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.

bash
go-migration migrate:rollback [--step=N] [--dry-run] [--lock-timeout=DURATION] [--no-lock]

Flags

FlagTypeDefaultDescription
--stepint0Number of migrations to roll back. 0 rolls back the entire last batch.
--dry-runboolfalsePrint the SQL that would run without executing it against the database
--lock-timeoutduration30sHow long to wait for the migration lock
--no-lockboolfalseDo not take the cross-process migration lock

Examples

bash
# Roll back the last batch
go-migration migrate:rollback

# Roll back the last 3 migrations
go-migration migrate:rollback --step=3

Make 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.

bash
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

FlagTypeDefaultDescription
--forceboolfalseSkip the confirmation prompt
--dry-runboolfalsePrint the SQL that would run without executing it against the database
--lock-timeoutduration30sHow long to wait for the migration lock
--no-lockboolfalseDo not take the cross-process migration lock

Examples

bash
# 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.json

migrate:refresh

Roll back all migrations and then re-run them. This is equivalent to running migrate:reset followed by migrate.

bash
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

FlagTypeDefaultDescription
--dry-runboolfalsePrint the SQL that would run without executing it against the database
--lock-timeoutduration30sHow long to wait for the migration lock
--no-lockboolfalseDo not take the cross-process migration lock

Examples

bash
# 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.json

migrate:fresh

Drop all tables from the database, then run all migrations from the beginning.

bash
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

FlagTypeDefaultDescription
--forceboolfalseSkip the confirmation prompt
--dry-runboolfalsePrint the SQL that would run without executing it against the database
--lock-timeoutduration30sHow long to wait for the migration lock
--no-lockboolfalseDo not take the cross-process migration lock

Examples

bash
# 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.json

migrate: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.

bash
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

FlagTypeDefaultDescription
--checkboolfalseAfter 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    Pending

Examples

bash
# 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.

bash
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

FlagTypeDefaultDescription
--lock-timeoutduration30sHow long to wait for the migration lock
--no-lockboolfalseDo 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

bash
# Create the migrations table
go-migration migrate:install

# Create with a specific config
go-migration migrate:install --config=./config/migration.json

make:migration

Generate a new migration file with boilerplate code. Use --create for new table migrations or --table for table modification migrations.

bash
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

FlagTypeDescription
--createstringGenerate a migration that creates a new table with the given name
--tablestringGenerate a migration that modifies an existing table with the given name

Examples

bash
# 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=users

Generated File: --create

When using --create=users, the generated file includes s.Create() scaffolding:

database/migrations/2024_01_01_090000_4821_create_users_table.go
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:

database/migrations/2024_02_01_141245_0937_add_email_to_users.go
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.

bash
go-migration make:seeder <name>

This command takes no flags beyond the global --config.

Examples

bash
# Generate a seeder file
go-migration make:seeder user

# Generate a seeder for a specific model
go-migration make:seeder user_profile

Generated File

database/seeders/user_seeder.go
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.

bash
go-migration make:factory <name>

Examples

bash
# Generate a factory file
go-migration make:factory users

# Generate a factory for a specific model
go-migration make:factory user_profile

Generated File

database/factories/users_factory.go
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.

bash
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

FlagTypeDescription
--classstringRun only the specified seeder by name
--tagstringRun only seeders matching the given tag
--lock-timeoutdurationHow long to wait for the migration lock (default 30s)
--no-lockboolDo 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

bash
# 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.json

Seeders 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.

bash
go-migration db:seed:rollback --class=CLASS [--lock-timeout=DURATION] [--no-lock]

Flags

FlagTypeDescription
--classstring(Required) The seeder name to roll back
--lock-timeoutdurationHow long to wait for the migration lock (default 30s)
--no-lockboolDo not take the cross-process migration lock

Examples

bash
# 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.json

The 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.

bash
go-migration db:seed:truncate --table=TABLE [--lock-timeout=DURATION] [--no-lock]

Flags

FlagTypeDescription
--tablestring(Required) The table name to truncate
--lock-timeoutdurationHow long to wait for the migration lock (default 30s)
--no-lockboolDo not take the cross-process migration lock

Examples

bash
# 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.json

This 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

FlagCommandsTypeDescription
--stepmigrate:rollbackintNumber of migrations to roll back
--dry-runmigrate, migrate:rollback, migrate:reset, migrate:refresh, migrate:freshboolPrint the SQL without executing it (requires the tracking table; run migrate:install first)
--lock-timeoutmigrate, migrate:rollback, migrate:reset, migrate:refresh, migrate:fresh, migrate:install, db:seed, db:seed:rollback, db:seed:truncatedurationHow long to wait for the migration lock (default 30s)
--no-lockSame commands as --lock-timeoutboolDo not take the cross-process migration lock
--checkmigrate:statusboolExit with status 1 if any migration is pending
--moduleinitstringOverride the Go module path used in generated imports
--forceinit, migrate:fresh, migrate:resetboolOverwrite existing files (init) or skip confirmation prompt (migrate:fresh, migrate:reset)
--createmake:migrationstringTable name for a create-table migration
--tablemake:migrationstringTable name for a table-modification migration
--tabledb:seed:truncatestringTable name to truncate (required)
--classdb:seedstringRun only the specified seeder by name
--classdb:seed:rollbackstringSeeder name to roll back (required)
--tagdb:seedstringRun seeders matching the given tag
(positional)make:migration, make:seeder, make:factorystringMigration/seeder/factory name (required, lower snake_case)
--configAll commandsstringPath 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