go-migrationGo Migration
Package Reference
Documentation

Migrator

Complete package reference for the go-migration Migrator — all public methods with signatures, parameters, return types, and usage examples.

Migrator API

The Migrator is the central component of go-migration. It manages migration registration, execution, rollback, and status tracking.

Import path: github.com/gopackx/go-migration/pkg/migrator

For conceptual documentation, see Migrations.


app.Run()

The all-in-one CLI runner. Handles argument parsing, config loading (migration.json with ${ENV} interpolation, falls back to go-migration.yaml), database connection, auto-discovery of migrations and seeders, grammar auto-resolution from driver name, and command dispatch.

Import path: github.com/gopackx/go-migration/pkg/cli/app

go
func Run()

On error, Run() prints the error to stderr and exits with status 1; on success it returns normally. migrate:status --check with pending migrations also exits with status 1.

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-project/database/migrations"
    _ "your-project/database/seeders"
)

func main() {
    app.Run()
}

Moved in v1.2.0. The CLI entry point used to be a Run() function in pkg/migrator; it now lives in its own package, pkg/cli/app. Replace the import github.com/gopackx/go-migration/pkg/migrator with github.com/gopackx/go-migration/pkg/cli/app in your cmd/migrator/main.go and call app.Run(). As a result, importing pkg/migrator as a library no longer pulls in the CLI, the config loader, the code generator, or the YAML parser.

Run() performs the following steps:

  1. Parses CLI arguments to determine the command
  2. For commands that need a database (smart classification skips DB for help, init, make:*):
    • Loads migration.json (or --config path) with ${ENV} variable interpolation. Falls back to go-migration.yaml for backward compatibility.
    • Opens a database connection using the config
    • Auto-discovers all registered migrations and seeders
    • Resolves the grammar from the driver field (postgres → PostgreSQL, mysql → MySQL, sqlite or its alias sqlite3 → SQLite)
    • Applies --dry-run, --lock-timeout, and --no-lock to the migrator, and picks the seeder dialect from the driver
  3. Dispatches the command

See Quick Start for a complete example.

New in v1.2.0: --lock-timeout and --no-lock on every command that writes, and --check on migrate:status. Since v1.0.0: --dry-run on the migrate commands, --tag on db:seed, --force on migrate:reset, and the db:seed:rollback and db:seed:truncate commands. See CLI Reference for details.

The CLI has no third-party CLI dependency: commands are dispatched by a small hand-written router in pkg/cli (a map[string]CommandHandler) on top of the standard library flag package. migration.json is the default configuration file.

Building on pkg/cli/commands directly? In v1.2.0 commands.MigratorRunner and commands.MigrationStatusInfo were removed. CommandContext.Migrator (and cli.CLIContext.Migrator) is now a commands.Migrator interface — Up, Rollback, Reset, Refresh, Fresh, and Status() ([]migrator.MigrationStatus, error) — which *migrator.Migrator satisfies directly, so no adapter is needed.


migrator.New()

Creates a new Migrator instance bound to a database connection.

go
func New(db *sql.DB, opts ...Option) *Migrator
ParameterTypeDescription
db*sql.DBThe database connection to run migrations against
opts...OptionOptional configuration (e.g., WithGrammar)

Returns: *Migrator

go
import (
    "database/sql"
    "github.com/gopackx/go-migration/pkg/migrator"
    "github.com/gopackx/go-migration/pkg/schema/grammars"
)

// PostgreSQL
m := migrator.New(db, migrator.WithGrammar(grammars.NewPostgresGrammar()))

// MySQL
m := migrator.New(db, migrator.WithGrammar(&grammars.MySQLGrammar{}))

Always pass the grammar matching your database: the schema builder and Fresh() need it, and without it the tracker falls back to PostgreSQL syntax. Defaults: tracking table "migrations", no grammar, no logger, migration lock enabled with DefaultLockTimeout (30s). Options only record settings — the runner and tracker are built once all options have been applied, so the order of options does not matter.

Available options:

OptionSignatureDescription
WithGrammarWithGrammar(g schema.Grammar) OptionSets the SQL grammar used by the runner, the tracking table, the lock, and Fresh() (required for Fresh())
WithTableNameWithTableName(name string) OptionSets the tracking table name (default: "migrations"). May be schema-qualified, e.g. "app.migrations"; the schema must exist
WithLoggerWithLogger(l Logger) OptionSets the logger used by the migrator and runner
WithAutoDiscoverWithAutoDiscover() OptionLoads auto-registered migrations during construction
WithProgressWithProgress(fn ProgressFunc) OptionRegisters a per-migration progress callback
WithDryRunWithDryRun(w io.Writer) OptionEnables dry-run mode, writing SQL to w instead of executing. Requires the tracking table to exist; never writes
WithLockTimeoutWithLockTimeout(d time.Duration) OptionHow long to wait for the cross-process migration lock (default DefaultLockTimeout, 30s; <= 0 means a single attempt)
WithoutLockWithoutLock() OptionDisables the cross-process migration lock

WithGrammar accepts any value implementing schema.Grammar. The grammars package provides &grammars.MySQLGrammar{}, &grammars.PostgresGrammar{}, and &grammars.SQLiteGrammar{} (or the matching grammars.NewMySQLGrammar(), grammars.NewPostgresGrammar(), and grammars.NewSQLiteGrammar() constructors).

The Logger interface required by WithLogger is:

go
type Logger interface {
    Info(msg string, args ...any)
    Error(msg string, args ...any)
}
go
// Custom table name and logger
m := migrator.New(db,
    migrator.WithGrammar(&grammars.PostgresGrammar{}),
    migrator.WithTableName("schema_migrations"),
    migrator.WithLogger(myLogger),
)

// Dry-run: print SQL to stdout instead of executing
m := migrator.New(db,
    migrator.WithGrammar(&grammars.PostgresGrammar{}),
    migrator.WithDryRun(os.Stdout),
)

See Database Grammars for grammar selection details.


migrator.WithLockTimeout() / migrator.WithoutLock()

Options that control the cross-process migration lock taken by Up, Rollback, Reset, Refresh, and Fresh.

go
const DefaultLockTimeout = 30 * time.Second

func WithLockTimeout(d time.Duration) Option
func WithoutLock() Option
OptionDescription
WithLockTimeout(d)How long to wait for the lock before failing with ErrLockTimeout. A zero or negative duration means a single attempt: fail immediately if another process holds the lock.
WithoutLock()Disable the lock. Use it only when a single process can ever run migrations against the database, or when the pool cannot provide the extra connection the lock needs.
go
m := migrator.New(db,
    migrator.WithGrammar(grammars.NewMySQLGrammar()),
    migrator.WithLockTimeout(2*time.Minute),
)

On PostgreSQL and MySQL the lock is held on a dedicated connection, so with locking enabled the pool must allow at least two open connections (MaxOpenConns >= 2, or 0 for unlimited). A pool limited to one connection is reported as an error. SQLite takes no lock.

See Concurrency and Locking.


m.Lock()

Acquires the cross-process migration lock and returns a function that releases it. Up, Rollback, Reset, Refresh, and Fresh take this lock themselves; Lock is exported so that other writers (such as seeders or data backfills) can be serialised with migrations.

go
func (m *Migrator) Lock(ctx context.Context) (release func() error, err error)
ParameterTypeDescription
ctxcontext.ContextCancels the wait for the lock

Returns: release func() error — releases the lock (safe to call more than once); err — wraps ErrLockTimeout if the lock timeout elapsed, or reports a pool limited to one connection.

go
release, err := m.Lock(ctx)
if err != nil {
    return err
}
defer release()

// ... work that must not overlap with migrations in other processes

The lock is polled with a short backoff (100 ms doubling up to 1 s) until it is acquired or the lock timeout elapses. If locking is disabled with WithoutLock or the grammar has no lock query (SQLite), Lock returns immediately with a no-op release. If releasing fails, the lock's connection is discarded so the server drops the lock.

Don't call Up, Rollback, Reset, Refresh, or Fresh while holding the lock returned by Lock — they would wait for it on another connection and fail with ErrLockTimeout.


migrator.NewTrackerWithGrammar()

Creates a Tracker for the migration tracking table whose SQL is generated by the given grammar. A nil grammar falls back to PostgreSQL syntax (which is also what NewTracker(db, tableName) uses).

go
func NewTracker(db *sql.DB, tableName string) *Tracker
func NewTrackerWithGrammar(db *sql.DB, tableName string, g schema.Grammar) *Tracker

The most common use is creating the tracking table up front — for example before a dry-run, which never creates it:

go
tracker := migrator.NewTrackerWithGrammar(db, "migrations", grammars.NewSQLiteGrammar())
if err := tracker.EnsureTable(); err != nil {
    log.Fatal(err)
}

EnsureTable() is idempotent. TableExists() (bool, error) reports whether the table exists without creating it. Tracker errors wrap ErrTrackingTable together with the driver's error.


migrator.AutoRegister()

Registers a migration in the global auto-registry. Intended to be called from init() functions in migration files.

go
func AutoRegister(name string, m Migration)
ParameterTypeDescription
namestringMigration name matching YYYYMMDDHHMMSS_description format
mMigrationPointer to a struct implementing the Migration interface

Panics if the name is invalid or a duplicate — fail-fast at startup.

migrations/20240101000001_create_users.go
package migrations

import "github.com/gopackx/go-migration/pkg/migrator"

func init() {
    migrator.AutoRegister("20240101000001_create_users", &CreateUsersTable{})
}

AutoRegister panics instead of returning an error because init() functions cannot return errors. This ensures configuration mistakes are caught immediately at startup.

See Registering Migrations for the full auto-discovery pattern.


migrator.GetAutoRegistered()

Returns all auto-registered migrations in timestamp-sorted order.

go
func GetAutoRegistered() []registeredMigration

Returns: []registeredMigration — a defensive copy of the auto-registry contents.


migrator.ResetAutoRegistry()

Clears the global auto-registry. Intended for testing only.

go
func ResetAutoRegistry()

migrator.WithAutoDiscover()

Returns an Option that loads all auto-registered migrations into the Migrator during construction.

go
func WithAutoDiscover() Option

Panics if a name conflict exists between auto-registered and manually registered migrations.

go
m := migrator.New(db,
    migrator.WithGrammar(grammars.NewPostgresGrammar()),
    migrator.WithAutoDiscover(),
)

See Registering Migrations for usage details.


migrator.WithProgress()

Returns an Option that registers a callback function to receive progress events during migration execution.

go
type ProgressEvent struct {
    Name      string
    Index     int
    Total     int
    Duration  time.Duration
    Direction string
}

type ProgressFunc func(event ProgressEvent)

func WithProgress(fn ProgressFunc) Option
FieldTypeDescription
NamestringThe migration name being executed
IndexintThe 0-based index of the current migration
TotalintThe total number of migrations to execute
Durationtime.DurationHow long this migration took to execute
DirectionstringEither "up" or "down"

The callback is invoked after each migration completes, giving you real-time visibility into the migration process.

go
m := migrator.New(db,
    migrator.WithGrammar(grammars.NewPostgresGrammar()),
    migrator.WithProgress(func(e migrator.ProgressEvent) {
        // Index is 0-based; add 1 for human-friendly "N of Total" display.
        fmt.Printf("[%d/%d] %s %s (%s)\n", e.Index+1, e.Total, e.Direction, e.Name, e.Duration)
    }),
)

See Progress Reporting.


m.Register()

Registers a migration struct with the migrator under a unique name.

go
func (m *Migrator) Register(name string, migration Migration) error
ParameterTypeDescription
namestringUnique migration name (must match the timestamp-prefixed format)
migrationMigrationPointer to a struct implementing the Migration interface

Returns: error — nil on success, or an error wrapping ErrInvalidMigrationName (bad name format) or ErrDuplicateMigration (name already registered).

go
if err := m.Register("20240101000001_create_users_table", &migrations.CreateUsersTable{}); err != nil {
    log.Fatal(err)
}
if err := m.Register("20240115000001_create_posts_table", &migrations.CreatePostsTable{}); err != nil {
    log.Fatal(err)
}

Migration names are sorted to determine execution order. Use a 14-digit YYYYMMDDHHMMSS_description prefix (or the YYYY_MM_DD_HHMMSS_RRRR_description form) for chronological ordering. The description must be lowercase.

See Registering Migrations for naming conventions and auto-discovery.


m.AutoDiscover()

Loads all migrations from the global auto-registry into the Migrator's internal registry.

go
func (m *Migrator) AutoDiscover() error

Returns: error — returns nil on success, or an error wrapping ErrDuplicateMigration if a name conflict exists with manually registered migrations.

go
m := migrator.New(db, migrator.WithGrammar(grammars.NewPostgresGrammar()))
if err := m.AutoDiscover(); err != nil {
    log.Fatal(err)
}

For most cases, use WithAutoDiscover() option instead of calling AutoDiscover() directly. The option handles errors by panicking, which is appropriate during initialization.

See Registering Migrations for the full auto-discovery workflow.


m.Up()

Executes all pending (not yet run) migrations in alphabetical order.

go
func (m *Migrator) Up() error

Returns: error — returns nil on success, or the first error encountered.

go
if err := m.Up(); err != nil {
    log.Fatal(err)
}

All migrations executed in a single Up() call are assigned the same batch number. Calling Up() multiple times is safe — already-executed migrations are skipped. Up() holds the migration lock for the whole operation and computes the pending list only once it holds it, so concurrent calls from several processes are safe. Each migration's tracking row is written in the migration's own transaction.

See Running Migrations for execution details and Batch Tracking for batch behavior.


m.Rollback()

Rolls back migrations by batch or by step count.

go
func (m *Migrator) Rollback(steps int) error
ParameterTypeDescription
stepsint0 to roll back the last batch, or a positive integer for step-based rollback

Returns: error

go
// Roll back the last batch
if err := m.Rollback(0); err != nil {
    log.Fatal(err)
}

// Roll back the last 3 migrations
if err := m.Rollback(3); err != nil {
    log.Fatal(err)
}

Rollback() holds the migration lock for the whole operation. See Rollback for detailed examples.


m.Reset()

Rolls back all executed migrations in reverse order by calling each migration's Down method.

go
func (m *Migrator) Reset() error

Returns: error

go
if err := m.Reset(); err != nil {
    log.Fatal(err)
}

Reset() holds the migration lock and, since v1.2.0, runs the before-migrate hooks for each migration like Up() and Rollback().

Reset() is destructive — all data in migrated tables will be lost.

See Reset, Refresh & Fresh.


m.Refresh()

Rolls back all migrations and then re-runs them. Equivalent to Reset() followed by Up(), under one migration lock held across both phases.

go
func (m *Migrator) Refresh() error

Returns: error

go
if err := m.Refresh(); err != nil {
    log.Fatal(err)
}

See Reset, Refresh & Fresh.


m.Fresh()

Drops all tables in the database and runs all migrations from scratch, in batch 1. Does not call Down methods. Requires WithGrammar. 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 off. A schema-qualified tracking table is dropped too. The lock is taken before anything is dropped.

go
func (m *Migrator) Fresh() error

Returns: error

go
if err := m.Fresh(); err != nil {
    log.Fatal(err)
}

Fresh() drops every table in the default schema, including the migrations tracking table. Never use on production.

See Reset, Refresh & Fresh.


m.Status()

Returns the current state of all registered migrations. Status() is read-only: it never creates the tracking table (every migration is pending if the table is absent) and does not take the migration lock.

go
func (m *Migrator) Status() ([]MigrationStatus, error)

Returns: []MigrationStatus, error

Each MigrationStatus contains:

FieldTypeDescription
NamestringThe migration name
AppliedboolWhether the migration has been applied
BatchintThe batch number (0 if not yet applied)
AppliedAt*time.TimeWhen the migration was applied (nil if not yet applied)
go
statuses, err := m.Status()
if err != nil {
    log.Fatal(err)
}

for _, s := range statuses {
    fmt.Printf("%s — Applied: %v, Batch: %d\n", s.Name, s.Applied, s.Batch)
}

See Migration Status.


m.BeforeMigrate()

Registers a hook that runs before each migration is executed. The hook fires once per migration, receiving that migration's name and direction.

go
func (m *Migrator) BeforeMigrate(fn func(name string, direction string) error)
ParameterTypeDescription
fnfunc(name string, direction string) errorCallback executed before each migration. direction is "up" or "down".

If the callback returns an error, that migration is aborted and no further migrations run.

go
m.BeforeMigrate(func(name, direction string) error {
    log.Printf("Starting %s %s...", direction, name)
    return nil
})

See Hooks for use cases and error behavior.


m.AfterMigrate()

Registers a hook that runs after each migration completes. The hook fires once per migration, receiving its name, direction, and execution duration.

go
func (m *Migrator) AfterMigrate(fn func(name string, direction string, duration time.Duration) error)
ParameterTypeDescription
fnfunc(name string, direction string, duration time.Duration) errorCallback executed after each migration. direction is "up" or "down".

If the callback returns an error, the error is ignored and execution continues.

go
m.AfterMigrate(func(name, direction string, duration time.Duration) error {
    log.Printf("Finished %s %s in %s", direction, name, duration)
    return nil
})

See Hooks for use cases and error behavior.


Migration Interface

Structs registered with the migrator must implement this interface:

go
type Migration interface {
    Up(s *schema.Builder) error
    Down(s *schema.Builder) error
}
MethodDescription
Up(*schema.Builder) errorDefines the forward migration
Down(*schema.Builder) errorDefines the reverse migration

Optionally implement TransactionOption to opt out of per-migration transaction wrapping. Return true to disable the surrounding transaction:

go
type TransactionOption interface {
    DisableTransaction() bool
}

See Defining Migrations and Transactions.


TaggedSeeder Interface

New in v1.0.0

Seeders can implement TaggedSeeder to be selectively run using the --tag flag on db:seed.

go
type TaggedSeeder interface {
    Tags() []string
}
MethodDescription
Tags() []stringReturns the tags this seeder belongs to
seeders/user_seeder.go
type UserSeeder struct{}

func (s *UserSeeder) Run(db *sql.DB) error { /* ... */ }
func (s *UserSeeder) Tags() []string { return []string{"core"} }
bash
# Run only seeders tagged "core"
go-migration db:seed --tag=core

See Seeder Tags for details.


RollbackableSeeder Interface

New in v1.0.0

Seeders can implement RollbackableSeeder to support rollback via the db:seed:rollback command.

go
type RollbackableSeeder interface {
    Rollback(db *sql.DB) error
}
MethodDescription
Rollback(*sql.DB) errorReverses the seeder's Run operation
seeders/user_seeder.go
type UserSeeder struct{}

func (s *UserSeeder) Run(db *sql.DB) error { /* ... */ }

func (s *UserSeeder) Rollback(db *sql.DB) error {
    _, err := db.Exec("DELETE FROM users WHERE seeded = true")
    return err
}
bash
go-migration db:seed:rollback --class=UserSeeder

See Seeder Rollback for details.


Sentinel Errors

ErrorDescription
ErrMigrationNotFoundA recorded migration is not registered
ErrDuplicateMigrationTwo migrations share a name
ErrInvalidMigrationNameA migration name does not match the expected format
ErrTrackingTableThe tracking table could not be created, read, or written (wraps the driver cause)
ErrLockTimeoutThe migration lock was not acquired within the lock timeout (v1.2.0)
ErrConnectionFailed, ErrConnectionNotFound, ErrTransactionFailedAliases of the database package errors
ErrDuplicateSeeder, ErrInvalidSeederName, ErrCircularDependency, ErrSeederNotFoundAliases of the seeder package errors
ErrUnsupportedTypeAlias of grammars.ErrUnsupportedType

ErrConfigValidation is no longer exported by migrator (removed in v1.2.0); use config.ErrConfigValidation. See Error Handling.


Quick Reference

MethodSignatureDescription
app.RunRun() (package pkg/cli/app)All-in-one CLI runner (config, DB, auto-discover, dispatch)
NewNew(db, ...Option) *MigratorCreate a new migrator
WithLockTimeoutWithLockTimeout(d) OptionOption to set the migration lock timeout (v1.2.0)
WithoutLockWithoutLock() OptionOption to disable the migration lock (v1.2.0)
LockLock(ctx) (func() error, error)Acquire the migration lock for your own work (v1.2.0)
NewTrackerWithGrammarNewTrackerWithGrammar(db, table, grammar) *TrackerDialect-aware tracking table access (v1.2.0)
AutoRegisterAutoRegister(name, migration)Register a migration via init()
WithAutoDiscoverWithAutoDiscover() OptionOption to load auto-registered migrations
WithProgressWithProgress(fn) OptionOption to receive per-migration progress events
RegisterRegister(name, migration) errorRegister a migration manually
AutoDiscoverAutoDiscover() errorLoad auto-registered migrations
UpUp() errorRun pending migrations
RollbackRollback(steps) errorRoll back migrations
ResetReset() errorRoll back all migrations
RefreshRefresh() errorReset + Up
FreshFresh() errorDrop all tables + Up
StatusStatus() ([]MigrationStatus, error)Get migration states (read-only)
BeforeMigrateBeforeMigrate(fn)Register per-migration pre-hook
AfterMigrateAfterMigrate(fn)Register per-migration post-hook
TaggedSeederTags() []stringInterface for tag-based seeder filtering (v1.0.0)
RollbackableSeederRollback(db) errorInterface for seeder rollback support (v1.0.0)