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
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.
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:
- Parses CLI arguments to determine the command
- For commands that need a database (smart classification skips DB for
help,init,make:*):- Loads
migration.json(or--configpath) with${ENV}variable interpolation. Falls back togo-migration.yamlfor backward compatibility. - Opens a database connection using the config
- Auto-discovers all registered migrations and seeders
- Resolves the grammar from the
driverfield (postgres→ PostgreSQL,mysql→ MySQL,sqliteor its aliassqlite3→ SQLite) - Applies
--dry-run,--lock-timeout, and--no-lockto the migrator, and picks the seeder dialect from the driver
- Loads
- 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.
func New(db *sql.DB, opts ...Option) *Migrator| Parameter | Type | Description |
|---|---|---|
db | *sql.DB | The database connection to run migrations against |
opts | ...Option | Optional configuration (e.g., WithGrammar) |
Returns: *Migrator
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:
| Option | Signature | Description |
|---|---|---|
WithGrammar | WithGrammar(g schema.Grammar) Option | Sets the SQL grammar used by the runner, the tracking table, the lock, and Fresh() (required for Fresh()) |
WithTableName | WithTableName(name string) Option | Sets the tracking table name (default: "migrations"). May be schema-qualified, e.g. "app.migrations"; the schema must exist |
WithLogger | WithLogger(l Logger) Option | Sets the logger used by the migrator and runner |
WithAutoDiscover | WithAutoDiscover() Option | Loads auto-registered migrations during construction |
WithProgress | WithProgress(fn ProgressFunc) Option | Registers a per-migration progress callback |
WithDryRun | WithDryRun(w io.Writer) Option | Enables dry-run mode, writing SQL to w instead of executing. Requires the tracking table to exist; never writes |
WithLockTimeout | WithLockTimeout(d time.Duration) Option | How long to wait for the cross-process migration lock (default DefaultLockTimeout, 30s; <= 0 means a single attempt) |
WithoutLock | WithoutLock() Option | Disables 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:
type Logger interface {
Info(msg string, args ...any)
Error(msg string, args ...any)
}// 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.
const DefaultLockTimeout = 30 * time.Second
func WithLockTimeout(d time.Duration) Option
func WithoutLock() Option| Option | Description |
|---|---|
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. |
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.
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.
func (m *Migrator) Lock(ctx context.Context) (release func() error, err error)| Parameter | Type | Description |
|---|---|---|
ctx | context.Context | Cancels 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.
release, err := m.Lock(ctx)
if err != nil {
return err
}
defer release()
// ... work that must not overlap with migrations in other processesThe 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).
func NewTracker(db *sql.DB, tableName string) *Tracker
func NewTrackerWithGrammar(db *sql.DB, tableName string, g schema.Grammar) *TrackerThe most common use is creating the tracking table up front — for example before a dry-run, which never creates it:
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.
func AutoRegister(name string, m Migration)| Parameter | Type | Description |
|---|---|---|
name | string | Migration name matching YYYYMMDDHHMMSS_description format |
m | Migration | Pointer to a struct implementing the Migration interface |
Panics if the name is invalid or a duplicate — fail-fast at startup.
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.
func GetAutoRegistered() []registeredMigrationReturns: []registeredMigration — a defensive copy of the auto-registry contents.
migrator.ResetAutoRegistry()
Clears the global auto-registry. Intended for testing only.
func ResetAutoRegistry()migrator.WithAutoDiscover()
Returns an Option that loads all auto-registered migrations into the Migrator during construction.
func WithAutoDiscover() OptionPanics if a name conflict exists between auto-registered and manually registered migrations.
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.
type ProgressEvent struct {
Name string
Index int
Total int
Duration time.Duration
Direction string
}
type ProgressFunc func(event ProgressEvent)
func WithProgress(fn ProgressFunc) Option| Field | Type | Description |
|---|---|---|
Name | string | The migration name being executed |
Index | int | The 0-based index of the current migration |
Total | int | The total number of migrations to execute |
Duration | time.Duration | How long this migration took to execute |
Direction | string | Either "up" or "down" |
The callback is invoked after each migration completes, giving you real-time visibility into the migration process.
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.
func (m *Migrator) Register(name string, migration Migration) error| Parameter | Type | Description |
|---|---|---|
name | string | Unique migration name (must match the timestamp-prefixed format) |
migration | Migration | Pointer 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).
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.
func (m *Migrator) AutoDiscover() errorReturns: error — returns nil on success, or an error wrapping ErrDuplicateMigration if a name conflict exists with manually registered migrations.
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.
func (m *Migrator) Up() errorReturns: error — returns nil on success, or the first error encountered.
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.
func (m *Migrator) Rollback(steps int) error| Parameter | Type | Description |
|---|---|---|
steps | int | 0 to roll back the last batch, or a positive integer for step-based rollback |
Returns: error
// 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.
func (m *Migrator) Reset() errorReturns: error
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.
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.
func (m *Migrator) Refresh() errorReturns: error
if err := m.Refresh(); err != nil {
log.Fatal(err)
}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.
func (m *Migrator) Fresh() errorReturns: error
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.
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.
func (m *Migrator) Status() ([]MigrationStatus, error)Returns: []MigrationStatus, error
Each MigrationStatus contains:
| Field | Type | Description |
|---|---|---|
Name | string | The migration name |
Applied | bool | Whether the migration has been applied |
Batch | int | The batch number (0 if not yet applied) |
AppliedAt | *time.Time | When the migration was applied (nil if not yet applied) |
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.
func (m *Migrator) BeforeMigrate(fn func(name string, direction string) error)| Parameter | Type | Description |
|---|---|---|
fn | func(name string, direction string) error | Callback executed before each migration. direction is "up" or "down". |
If the callback returns an error, that migration is aborted and no further migrations run.
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.
func (m *Migrator) AfterMigrate(fn func(name string, direction string, duration time.Duration) error)| Parameter | Type | Description |
|---|---|---|
fn | func(name string, direction string, duration time.Duration) error | Callback executed after each migration. direction is "up" or "down". |
If the callback returns an error, the error is ignored and execution continues.
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:
type Migration interface {
Up(s *schema.Builder) error
Down(s *schema.Builder) error
}| Method | Description |
|---|---|
Up(*schema.Builder) error | Defines the forward migration |
Down(*schema.Builder) error | Defines the reverse migration |
Optionally implement TransactionOption to opt out of per-migration transaction wrapping. Return true to disable the surrounding transaction:
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.
type TaggedSeeder interface {
Tags() []string
}| Method | Description |
|---|---|
Tags() []string | Returns the tags this seeder belongs to |
type UserSeeder struct{}
func (s *UserSeeder) Run(db *sql.DB) error { /* ... */ }
func (s *UserSeeder) Tags() []string { return []string{"core"} }# Run only seeders tagged "core"
go-migration db:seed --tag=coreSee Seeder Tags for details.
RollbackableSeeder Interface
New in v1.0.0
Seeders can implement RollbackableSeeder to support rollback via the db:seed:rollback command.
type RollbackableSeeder interface {
Rollback(db *sql.DB) error
}| Method | Description |
|---|---|
Rollback(*sql.DB) error | Reverses the seeder's Run operation |
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
}go-migration db:seed:rollback --class=UserSeederSee Seeder Rollback for details.
Sentinel Errors
| Error | Description |
|---|---|
ErrMigrationNotFound | A recorded migration is not registered |
ErrDuplicateMigration | Two migrations share a name |
ErrInvalidMigrationName | A migration name does not match the expected format |
ErrTrackingTable | The tracking table could not be created, read, or written (wraps the driver cause) |
ErrLockTimeout | The migration lock was not acquired within the lock timeout (v1.2.0) |
ErrConnectionFailed, ErrConnectionNotFound, ErrTransactionFailed | Aliases of the database package errors |
ErrDuplicateSeeder, ErrInvalidSeederName, ErrCircularDependency, ErrSeederNotFound | Aliases of the seeder package errors |
ErrUnsupportedType | Alias of grammars.ErrUnsupportedType |
ErrConfigValidation is no longer exported by migrator (removed in v1.2.0); use config.ErrConfigValidation. See Error Handling.
Quick Reference
| Method | Signature | Description |
|---|---|---|
app.Run | Run() (package pkg/cli/app) | All-in-one CLI runner (config, DB, auto-discover, dispatch) |
New | New(db, ...Option) *Migrator | Create a new migrator |
WithLockTimeout | WithLockTimeout(d) Option | Option to set the migration lock timeout (v1.2.0) |
WithoutLock | WithoutLock() Option | Option to disable the migration lock (v1.2.0) |
Lock | Lock(ctx) (func() error, error) | Acquire the migration lock for your own work (v1.2.0) |
NewTrackerWithGrammar | NewTrackerWithGrammar(db, table, grammar) *Tracker | Dialect-aware tracking table access (v1.2.0) |
AutoRegister | AutoRegister(name, migration) | Register a migration via init() |
WithAutoDiscover | WithAutoDiscover() Option | Option to load auto-registered migrations |
WithProgress | WithProgress(fn) Option | Option to receive per-migration progress events |
Register | Register(name, migration) error | Register a migration manually |
AutoDiscover | AutoDiscover() error | Load auto-registered migrations |
Up | Up() error | Run pending migrations |
Rollback | Rollback(steps) error | Roll back migrations |
Reset | Reset() error | Roll back all migrations |
Refresh | Refresh() error | Reset + Up |
Fresh | Fresh() error | Drop all tables + Up |
Status | Status() ([]MigrationStatus, error) | Get migration states (read-only) |
BeforeMigrate | BeforeMigrate(fn) | Register per-migration pre-hook |
AfterMigrate | AfterMigrate(fn) | Register per-migration post-hook |
TaggedSeeder | Tags() []string | Interface for tag-based seeder filtering (v1.0.0) |
RollbackableSeeder | Rollback(db) error | Interface for seeder rollback support (v1.0.0) |
Error Handling
Handle and diagnose errors from go-migration using typed error values, errors.Is(), errors.As(), and a troubleshooting guide for common scenarios.
Schema Builder
Complete package reference for the Schema Builder, Blueprint, and Grammar — all public methods with signatures, parameters, return types, and usage examples.