go-migrationGo Migration
Getting Started
Documentation

Quick Start

Create and run your first database migration with go-migration in minutes.

Quick Start

This guide walks you through creating and running your first migration with go-migration v1.2.0. By the end, you'll have a users table in your database.

Initialize Your Project

The fastest way to get started is with go-migration init. This command scaffolds all the directories and files you need:

bash
go-migration init

This creates:

  • database/migrations/, database/seeders/, database/factories/ directories
  • cmd/migrator/main.go — entry point that calls app.Run() (from github.com/gopackx/go-migration/pkg/cli/app)
  • migration.json — config file with environment variable placeholders (JSON format)

The command auto-detects your module path from go.mod. See the CLI Reference for flags like --module and --force.

If you prefer to set up manually, you can skip this step and create the config file and directories yourself — see the steps below.

Create a Config File

If you ran go-migration init, a migration.json was already created in your project root with sensible defaults. Open it and fill in your database credentials:

migration.json
{
  "default": "postgres",
  "connections": {
    "postgres": {
      "driver": "postgres",
      "host": "${DB_HOST}",
      "port": 5432,
      "database": "${DB_NAME}",
      "username": "${DB_USER}",
      "password": "${DB_PASSWORD}",
      "sslmode": "disable"
    }
  },
  "migration_dir": "database/migrations",
  "seeder_dir": "database/seeders",
  "factory_dir": "database/factories",
  "migration_table": "migrations",
  "log_level": "info"
}

If you prefer YAML, create a go-migration.yaml manually. Note: YAML format is deprecated since v1.0.0 and will show a deprecation warning when loaded.

go-migration.yaml
default: postgres

connections:
  postgres:
    driver: postgres
    host: ${DB_HOST}
    port: 5432
    database: ${DB_NAME}
    username: ${DB_USER}
    password: ${DB_PASSWORD}
    sslmode: disable

migration_dir: database/migrations
seeder_dir: database/seeders
factory_dir: database/factories
migration_table: migrations
log_level: info

${VAR_NAME} placeholders are replaced with environment variable values at load time. See Configuration for all supported fields.

Define a Migration

Create a migration file with an init() function for auto-registration, plus Up and Down methods:

database/migrations/2025_01_15_143022_1234_create_users_table.go
package migrations

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

func init() {
    migrator.AutoRegister("2025_01_15_143022_1234_create_users_table", &CreateUsersTable{})
}

type CreateUsersTable struct{}

func (m *CreateUsersTable) Up(s *schema.Builder) error {
    return s.Create("users", func(bp *schema.Blueprint) {
        bp.ID()
        bp.String("name", 255)
        bp.String("email", 255).Unique()
        bp.Boolean("active").Default(true)
        bp.Timestamps()
    })
}

func (m *CreateUsersTable) Down(s *schema.Builder) error {
    return s.Drop("users")
}

The init() function registers the migration automatically when the package is imported. The Up method creates the table, and Down reverses it.

Migration filenames in v1.0.0 use the format YYYY_MM_DD_HHMMSS_RRRR_description.go, where RRRR is a random 4-digit number to prevent timestamp collisions. The make:migration command generates this automatically. The old format (YYYYMMDDHHMMSS_description.go) is still supported.

Set Up and Run

app.Run() is the all-in-one CLI runner. It handles config loading, database connection, auto-discovery of migrations and seeders, grammar auto-resolution, and command dispatch — all in a single call. This is exactly what go-migration init generates in cmd/migrator/main.go:

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" // blank import triggers init() registration
    _ "your-project/database/seeders"    // blank import triggers seeder init() registration
)

func main() {
    app.Run()
}

The three driver imports register the database/sql drivers; keep only the ones you use.

Upgrading from v1.1.x: migrator.Run() was removed in v1.2.0. Replace the import github.com/gopackx/go-migration/pkg/migrator with github.com/gopackx/go-migration/pkg/cli/app, call app.Run() instead of migrator.Run(), and replace _ "github.com/mattn/go-sqlite3" with _ "modernc.org/sqlite". The CLI now lives in its own package, so importing pkg/migrator as a library no longer pulls in the CLI, the config loader, or the YAML parser. See the Changelog.

That's it. app.Run() will:

  1. Parse CLI arguments (migrate, db:seed, migrate:fresh, etc.)
  2. Load migration.json (with ${ENV} interpolation, falls back to go-migration.yaml)
  3. Open a database connection
  4. Auto-discover all registered migrations and seeders
  5. Resolve the correct grammar from the driver name
  6. Execute the requested command

If you need more control, you can set up the migrator manually:

main.go
package main

import (
    "database/sql"
    "log"

    _ "github.com/lib/pq" // PostgreSQL driver

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

    "github.com/gopackx/go-migration/pkg/schema/grammars"
    _ "your-project/database/migrations" // blank import triggers init() registration
)

func main() {
    db, err := sql.Open("postgres", "postgres://user:password@localhost:5432/mydb?sslmode=disable")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

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

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

    log.Println("Migrations completed successfully!")
}

The blank import _ "your-project/database/migrations" triggers all init() functions, and WithAutoDiscover() loads them into the migrator.

Run It

bash
# With app.Run() — use CLI commands directly
go run ./cmd/migrator migrate

# Or run all pending migrations with manual setup
go run main.go

go-migration will:

  1. Take the cross-process migration lock (see Concurrency and Locking)
  2. Create a migrations tracking table (if it doesn't exist)
  3. Check which migrations have already run
  4. Execute the Up method of any pending migrations
  5. Record each migration in the tracking table with a batch number, inside the migration's own transaction

Replace the connection string or environment variables with your actual database credentials. The example above uses PostgreSQL — see Database Grammars for MySQL and SQLite setup.

Using app.Run()

With app.Run(), your binary becomes a full CLI tool. All standard commands are available:

bash
go run ./cmd/migrator migrate              # Run pending migrations
go run ./cmd/migrator migrate --dry-run    # Preview SQL without applying (run migrate:install first)
go run ./cmd/migrator migrate:rollback     # Roll back last batch
go run ./cmd/migrator migrate:fresh        # Drop all tables + re-migrate
go run ./cmd/migrator migrate:refresh      # Reset + re-migrate
go run ./cmd/migrator migrate:status       # Show migration status (read-only)
go run ./cmd/migrator migrate:status --check  # Exit 1 if anything is pending
go run ./cmd/migrator db:seed              # Run all seeders
go run ./cmd/migrator db:seed --class=UserSeeder  # Run specific seeder
go run ./cmd/migrator db:seed --tag=core   # Run seeders by tag
go run ./cmd/migrator make:migration create_posts # Scaffold a migration file (lower snake_case name)

app.Run() uses smart command classification — commands like help, init, and make:* skip database setup entirely, so they work even without a valid database connection.

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

Using a Different Database

go-migration supports PostgreSQL, MySQL, and SQLite. With app.Run(), the grammar is auto-resolved from the driver field in your config file — no manual grammar selection needed.

For manual setup, swap the driver import and grammar:

MySQL example
import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"

    "github.com/gopackx/go-migration/pkg/migrator"
    "github.com/gopackx/go-migration/pkg/schema/grammars"
    _ "your-project/database/migrations"
)

func main() {
    db, _ := sql.Open("mysql", "user:password@tcp(localhost:3306)/mydb")

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

    // Run as before...
}
SQLite example
import (
    "database/sql"
    _ "modernc.org/sqlite" // pure Go, registers as "sqlite"

    "github.com/gopackx/go-migration/pkg/migrator"
    "github.com/gopackx/go-migration/pkg/schema/grammars"
    _ "your-project/database/migrations"
)

func main() {
    db, _ := sql.Open("sqlite", "./app.db")

    m := migrator.New(db,
        migrator.WithGrammar(&grammars.SQLiteGrammar{}),
        migrator.WithAutoDiscover(),
    )

    // Run as before...
}

Rolling Back

To undo the last batch of migrations:

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

This calls the Down method on each migration in the last batch, in reverse order.

What's Next?