Hooks
Execute custom logic before and after migrations using BeforeMigrate and AfterMigrate hooks in go-migration.
Hooks
go-migration lets you register hooks that run custom logic before and after migration execution. Hooks are useful for logging, sending notifications, clearing caches, or validating preconditions.
Two hooks are available:
m.BeforeMigrate()— runs before each migration is appliedm.AfterMigrate()— runs after each migration is applied
Hooks fire per migration, not once for the whole run. If a single m.Up()
applies three migrations, each hook is invoked three times — once for each
migration. The same applies to Rollback(), Reset(), Refresh(), and
Fresh(), where hooks fire for each migration that runs.
BeforeMigrate
Register a function that runs before each migration executes. The callback receives the migration name and the direction ("up" or "down") and returns an error.
m.BeforeMigrate(func(name, direction string) error {
log.Printf("Starting %s %s...", direction, name)
return nil
})If a BeforeMigrate hook returns an error, that migration is aborted and no further migrations run.
m.BeforeMigrate(func(name, direction string) error {
if !isMaintenanceWindow() {
return fmt.Errorf("migrations are only allowed during maintenance windows")
}
return nil
})A BeforeMigrate hook error stops the migration process at the current
migration. Migrations already applied in this run remain applied. Use this to
enforce preconditions before each schema change.
AfterMigrate
Register a function that runs after each migration completes. The callback receives the migration name, the direction ("up" or "down"), and the duration it took, and returns an error.
m.AfterMigrate(func(name, direction string, duration time.Duration) error {
log.Printf("Finished %s %s in %s", direction, name, duration)
return nil
})If an AfterMigrate hook returns an error, the error is ignored and execution continues. The schema change remains applied.
m.AfterMigrate(func(name, direction string, duration time.Duration) error {
if err := notifySlack(fmt.Sprintf("%s %s applied", direction, name)); err != nil {
return fmt.Errorf("failed to send Slack notification: %w", err)
}
return nil
})AfterMigrate hook errors are ignored without rolling back. This is by design —
the migration has already been applied and committed, so the hook failure is
treated as a non-critical side effect.
Error Behavior Summary
| Hook | Signature | On Error | Migrations |
|---|---|---|---|
BeforeMigrate | func(name, direction string) error | Aborts current migration | Earlier ones stay applied; current and later not executed |
AfterMigrate | func(name, direction string, duration time.Duration) error | Ignored | Already applied, not rolled back |
Complete Example
Here's a full example using both hooks for logging and notifications:
package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/lib/pq"
"github.com/gopackx/go-migration/pkg/migrator"
)
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)
// Runs before each migration: log it and validate preconditions
m.BeforeMigrate(func(name, direction string) error {
log.Printf("[%s] %s starting at %s", direction, name, time.Now().Format(time.RFC3339))
// Example: check a precondition before allowing this migration
if err := db.Ping(); err != nil {
return fmt.Errorf("database is not reachable: %w", err)
}
return nil
})
// Runs after each migration: log completion and send a notification
m.AfterMigrate(func(name, direction string, duration time.Duration) error {
log.Printf("[%s] %s completed in %s", direction, name, duration)
// Example: send a notification (failure won't roll back the migration)
if err := sendNotification(fmt.Sprintf("%s %s applied", direction, name)); err != nil {
return fmt.Errorf("notification failed: %w", err)
}
return nil
})
// Register migrations...
// m.Register("20240101000001_create_users_table", &CreateUsersTable{})
if err := m.Up(); err != nil {
log.Fatal(err)
}
}
func sendNotification(message string) error {
// Your notification logic (Slack, email, webhook, etc.)
log.Println(message)
return nil
}Use Cases
- Logging — record when each migration starts and finishes (with its name, direction, and duration) for audit trails
- Notifications — send Slack or email alerts as migrations run in production
- Precondition checks — verify the database is reachable or that a maintenance window is active before applying each change
- Cache invalidation — clear application caches after schema changes
What's Next?
- CLI Reference — run migrations from the command line
- Configuration — configure go-migration with YAML, JSON, or environment variables
- Error Handling — handle and diagnose migration errors