Skip to content
Migrations

Migrations

Steward versions schema changes the boring, reliable way: ordered migrations with batches and rollback — not AutoMigrate drift. The public migrate package depends only on GORM.

Writing migrations

A migration is Go code — full access to your models and to raw SQL:

// migrations/20260728120000_add_featured.go
package migrations

import (
    "gorm.io/gorm"
    "github.com/imfiqhan/steward/migrate"
)

func init() {
    All = append(All, migrate.Migration{
        Name: "20260728120000_add_featured",
        Up: func(tx *gorm.DB) error {
            return tx.Exec(`ALTER TABLE posts ADD COLUMN featured boolean NOT NULL DEFAULT false`).Error
        },
        Down: func(tx *gorm.DB) error {
            return tx.Exec(`ALTER TABLE posts DROP COLUMN featured`).Error
        },
    })
}

steward make:migration add_featured scaffolds the file with a timestamped name; steward make:resource writes the table-creation migration for you. Each migration runs in a transaction; a failure rolls back and stops the run.

Running them

go run . migrate up               # apply everything pending (one batch)
go run . migrate status           # source, name, batch, applied-at
go run . migrate down             # roll back the last batch
go run . migrate down -steps 2    # or exactly N migrations

Applied state lives in the admin_migrations table with two sources: core (Steward’s own tables — users, roles, permissions, menu, logs, settings) and app (yours). Core migrations also seed the first admin / admin account and the default menu.

Production

By default Build() applies pending core migrations so development “just works”. For production, take control:

steward.New(steward.Config{ ..., DisableAutoMigrate: true })

and run ./app migrate up as an explicit release step, before the new serve/worker processes start.