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:
type Migration interface {
Up(s *schema.Builder)
Down(s *schema.Builder)
}| Method | Purpose |
|---|---|
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.goFor example: 2025_01_15_143022_1234_create_users_table.go
Each segment of the filename serves a specific purpose:
| Segment | Example | Description |
|---|---|---|
YYYY | 2025 | Year (4 digits) |
MM | 01 | Month (2 digits) |
DD | 15 | Day (2 digits) |
HHMMSS | 143022 | Hour, minute, second (6 digits) |
RRRR | 1234 | Random 4-digit number (prevents collision) |
description | create_users_table | Migration 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:
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 callingmigrator.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:
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
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?
- Registering Migrations — how to register your migration structs with the migrator
- Schema Builder — full reference for the table and column API