go-migrationgo-migration
Migrations
Documentation

Defining Migrations

Learn how to define database migrations using the Migration interface with Up and Down methods.

Defining Migrations

Migrations in go-migration are Go structs that implement the Migration interface. Each migration describes a change to your database schema — creating a table, adding a column, creating an index, and so on.

The Migration Interface

Every migration must implement two methods:

go
type Migration interface {
    Up(s *schema.Builder)
    Down(s *schema.Builder)
}
MethodPurpose
Up(*schema.Builder)Defines the forward migration — what changes to apply
Down(*schema.Builder)Defines the reverse migration — how to undo the changes

Both methods receive a *schema.Builder that provides the fluent API for creating, altering, and dropping tables.

Migration Naming Format

When you run make:migration, go-migration generates files using the following naming format:

YYYY_MM_DD_HHMMSS_RRRR_description.go

For example: 2025_01_15_143022_1234_create_users_table.go

Each segment of the filename serves a specific purpose:

SegmentExampleDescription
YYYY2025Year (4 digits)
MM01Month (2 digits)
DD15Day (2 digits)
HHMMSS143022Hour, minute, second (6 digits)
RRRR1234Random 4-digit number (prevents collision)
descriptioncreate_users_tableMigration description (snake_case)

The random 4-digit segment (RRRR) prevents timestamp collisions when creating multiple migrations in quick succession. This ensures each migration file has a unique name even if two developers generate migrations at the exact same second.

The old naming format (YYYYMMDDHHMMSS_description.go, e.g. 20240101000001_create_users.go) is still accepted and fully backward compatible. Both formats can coexist in the same project. The new format is the default for files generated by make:migration starting in v1.0.0.

Writing a Migration

Create a struct and implement the Up and Down methods:

database/migrations/2025_01_15_143022_1234_create_users_table.go
package migrations

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

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

type CreateUsersTable struct{}

func (m *CreateUsersTable) Up(s *schema.Builder) {
    s.Create("users", func(bp *schema.Blueprint) {
        bp.ID("id")
        bp.String("name", 255)
        bp.String("email", 255).Unique()
        bp.Boolean("active").Default(true)
        bp.Timestamp("created_at").Nullable()
        bp.Timestamp("updated_at").Nullable()
    })
}

func (m *CreateUsersTable) Down(s *schema.Builder) {
    s.DropIfExists("users")
}

The Up method creates a users table with columns for id, name, email, active status, and timestamps. The Down method drops the table to reverse the migration. The init() function automatically registers the migration with the migrator using AutoRegister(), so it's discovered when the package is imported.

Migration Struct Conventions

  • Each migration is a separate Go struct (e.g., CreateUsersTable, AddPhoneToUsers)
  • Structs are typically empty — they only need to satisfy the interface
  • Place migration files in a dedicated database/migrations/ package
  • Name files using the naming format: 2025_01_15_143022_1234_create_users_table.go (see Migration Naming Format)
  • Include an init() function calling migrator.AutoRegister() for auto-discovery (recommended)

Self-Registration with init()

The recommended pattern is to have each migration file register itself using init(). Starting in v1.0.0, the make:migration command automatically generates this boilerplate for you:

database/migrations/2025_01_15_143022_1234_create_users_table.go
package migrations

import (
    "github.com/gopackx/go-migration/migrator"
    "github.com/gopackx/go-migration/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.Timestamps()
    })
}

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

This makes each migration file self-contained — the name, struct, and registration are all in one place. The init() function runs automatically when the package is imported, calling migrator.AutoRegister() to register the migration with its filename as the key. See Registering Migrations for the full auto-discovery setup.

Another Example: Adding a Column

database/migrations/2025_02_01_100500_5678_add_phone_to_users.go
package migrations

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

func init() {
    migrator.AutoRegister("2025_02_01_100500_5678_add_phone_to_users", &AddPhoneToUsers{})
}

type AddPhoneToUsers struct{}

func (m *AddPhoneToUsers) Up(s *schema.Builder) error {
    return s.Alter("users", func(bp *schema.Blueprint) {
        bp.String("phone", 20).Nullable()
    })
}

func (m *AddPhoneToUsers) Down(s *schema.Builder) error {
    return s.Alter("users", func(bp *schema.Blueprint) {
        bp.DropColumn("phone")
    })
}

Always implement the Down method so your migrations are fully reversible. This makes rollbacks and resets reliable.

What's Next?