GORM provides automatic schema migration functionality to keep your database schema in sync with your Go models.
Automatically migrate your schema to keep it up to date.
func (db *DB) AutoMigrate(dst ...interface{}) errorUsage:
// Migrate single model
db.AutoMigrate(&User{})
// Migrate multiple models
db.AutoMigrate(&User{}, &Order{}, &Product{})
// Check for errors
if err := db.AutoMigrate(&User{}); err != nil {
// Handle migration error
}What AutoMigrate does:
Get the migrator for manual schema operations.
func (db *DB) Migrator() MigratorThe Migrator interface provides comprehensive schema management methods.
type Migrator interface {
// AutoMigrate
AutoMigrate(dst ...interface{}) error
// Database
CurrentDatabase() string
// Tables
CreateTable(dst ...interface{}) error
DropTable(dst ...interface{}) error
HasTable(dst interface{}) bool
RenameTable(oldName, newName interface{}) error
GetTables() (tableList []string, err error)
TableType(dst interface{}) (TableType, error)
// Columns
AddColumn(dst interface{}, field string) error
DropColumn(dst interface{}, field string) error
AlterColumn(dst interface{}, field string) error
MigrateColumn(dst interface{}, field *schema.Field, columnType ColumnType) error
HasColumn(dst interface{}, field string) bool
RenameColumn(dst interface{}, oldName, newName string) error
ColumnTypes(dst interface{}) ([]ColumnType, error)
// Views
CreateView(name string, option ViewOption) error
DropView(name string) error
// Constraints
CreateConstraint(dst interface{}, name string) error
DropConstraint(dst interface{}, name string) error
HasConstraint(dst interface{}, name string) bool
// Indexes
CreateIndex(dst interface{}, name string) error
DropIndex(dst interface{}, name string) error
HasIndex(dst interface{}, name string) bool
RenameIndex(dst interface{}, oldName, newName string) error
GetIndexes(dst interface{}) ([]Index, error)
// Data Types
FullDataTypeOf(*schema.Field) clause.Expr
GetTypeAliases(databaseTypeName string) []string
}Create tables for models.
m := db.Migrator()
// Create single table
m.CreateTable(&User{})
// Create multiple tables
m.CreateTable(&User{}, &Order{}, &Product{})Drop tables.
m := db.Migrator()
// Drop single table
m.DropTable(&User{})
// Drop multiple tables
m.DropTable(&User{}, &Order{})
// Drop table by name
m.DropTable("users")m := db.Migrator()
// Check if table exists
if m.HasTable(&User{}) {
// Table exists
}
// Check by name
if m.HasTable("users") {
// Table exists
}m := db.Migrator()
// Rename table
m.RenameTable(&User{}, &Customer{})
// Rename by name
m.RenameTable("users", "customers")m := db.Migrator()
tables, err := m.GetTables()
if err != nil {
// Handle error
}
for _, table := range tables {
fmt.Println(table)
}m := db.Migrator()
tableType, err := m.TableType(&User{})
if err != nil {
// Handle error
}
fmt.Println("Schema:", tableType.Schema())
fmt.Println("Name:", tableType.Name())
fmt.Println("Type:", tableType.Type())
if comment, ok := tableType.Comment(); ok {
fmt.Println("Comment:", comment)
}Add a new column to a table.
m := db.Migrator()
type User struct {
ID uint
Name string
Age int // New field
}
// Add the Age column
m.AddColumn(&User{}, "Age")Drop a column from a table.
m := db.Migrator()
m.DropColumn(&User{}, "Age")m := db.Migrator()
if m.HasColumn(&User{}, "Age") {
// Column exists
}m := db.Migrator()
m.RenameColumn(&User{}, "Name", "FullName")Change a column's type or constraints.
m := db.Migrator()
type User struct {
ID uint
Name string `gorm:"size:256"` // Change size
}
// Alter the Name column to match new definition
m.AlterColumn(&User{}, "Name")Get information about all columns in a table.
m := db.Migrator()
columnTypes, err := m.ColumnTypes(&User{})
if err != nil {
// Handle error
}
for _, col := range columnTypes {
fmt.Println("Name:", col.Name())
fmt.Println("Type:", col.DatabaseTypeName())
if length, ok := col.Length(); ok {
fmt.Println("Length:", length)
}
if nullable, ok := col.Nullable(); ok {
fmt.Println("Nullable:", nullable)
}
if unique, ok := col.Unique(); ok {
fmt.Println("Unique:", unique)
}
if primaryKey, ok := col.PrimaryKey(); ok {
fmt.Println("Primary Key:", primaryKey)
}
}Create an index defined in the model.
type User struct {
gorm.Model
Name string `gorm:"index"`
Email string `gorm:"uniqueIndex"`
}
m := db.Migrator()
// Create specific index
m.CreateIndex(&User{}, "Name")
m.CreateIndex(&User{}, "Email")
// Index names are generated automatically, or specify with idx_name
type User struct {
gorm.Model
Name string `gorm:"index:idx_name"`
}
m.CreateIndex(&User{}, "idx_name")m := db.Migrator()
m.DropIndex(&User{}, "idx_name")m := db.Migrator()
if m.HasIndex(&User{}, "idx_name") {
// Index exists
}m := db.Migrator()
m.RenameIndex(&User{}, "old_idx_name", "new_idx_name")m := db.Migrator()
indexes, err := m.GetIndexes(&User{})
if err != nil {
// Handle error
}
for _, idx := range indexes {
fmt.Println("Name:", idx.Name())
fmt.Println("Table:", idx.Table())
fmt.Println("Columns:", idx.Columns())
if primaryKey, ok := idx.PrimaryKey(); ok {
fmt.Println("Primary Key:", primaryKey)
}
if unique, ok := idx.Unique(); ok {
fmt.Println("Unique:", unique)
}
fmt.Println("Options:", idx.Option())
}Create a foreign key or check constraint defined in the model.
type User struct {
gorm.Model
CompanyID int
Company Company `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}
m := db.Migrator()
// Create the constraint
m.CreateConstraint(&User{}, "Company")
// Or by constraint name
m.CreateConstraint(&User{}, "fk_users_company")m := db.Migrator()
m.DropConstraint(&User{}, "Company")
m.DropConstraint(&User{}, "fk_users_company")m := db.Migrator()
if m.HasConstraint(&User{}, "Company") {
// Constraint exists
}Create a database view.
type ViewOption struct {
Replace bool // Replace existing view
CheckOption string // Check option clause
Query *DB // Query for view
}Usage:
m := db.Migrator()
// Create view
m.CreateView("active_users", gorm.ViewOption{
Query: db.Model(&User{}).Where("active = ?", true),
})
// Create or replace view
m.CreateView("active_users", gorm.ViewOption{
Replace: true,
Query: db.Model(&User{}).Where("active = ?", true),
})m := db.Migrator()
m.DropView("active_users")Configure migration behavior globally or per-session.
// Disable foreign key constraints during migration
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
DisableForeignKeyConstraintWhenMigrating: true,
})
// Ignore relationships when migrating
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
IgnoreRelationshipsWhenMigrating: true,
})Use struct tags to control migration behavior.
type User struct {
gorm.Model
Name string `gorm:"size:256;not null;default:'unknown'"`
Email string `gorm:"uniqueIndex;size:256"`
Age int `gorm:"check:age >= 0"`
Active bool `gorm:"default:true"`
CompanyID int
Company Company `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
// Ignore this field in migration
TempData string `gorm:"-:migration"`
}
// Common migration tags:
// - size:256 Set column size
// - not null NOT NULL constraint
// - default:value Default value
// - unique UNIQUE constraint
// - uniqueIndex Create unique index
// - index Create index
// - check:expr CHECK constraint
// - constraint:... Foreign key constraint options
// - -:migration Ignore field in migration
// - comment:text Column commentDefine indexes using struct tags or models.
type User struct {
gorm.Model
// Simple index
Name string `gorm:"index"`
// Named index
Email string `gorm:"index:idx_email"`
// Unique index
Username string `gorm:"uniqueIndex"`
// Composite index
FirstName string `gorm:"index:idx_name"`
LastName string `gorm:"index:idx_name"`
// Index with options
Code string `gorm:"index:,type:btree,length:10,sort:desc"`
}Define check constraints for data validation.
type User struct {
gorm.Model
Name string
Age int `gorm:"check:age >= 0"`
Role string `gorm:"check:role IN ('admin', 'user', 'guest')"`
}
// Multiple constraints
type User struct {
gorm.Model
Age int `gorm:"check:age >= 0,age_positive;check:age <= 120,age_max"`
Salary int `gorm:"check:salary >= 0"`
}Get the current database name.
m := db.Migrator()
dbName := m.CurrentDatabase()
fmt.Println("Current database:", dbName)Implement custom data types for migration.
import "gorm.io/gorm/schema"
type GormDataTypeInterface interface {
GormDataType() string
}
type BuildIndexOptionsInterface interface {
BuildIndexOptions([]schema.IndexOption, *gorm.Statement) []interface{}
}Usage:
import "gorm.io/gorm/migrator"
type JSON json.RawMessage
// GormDataType returns the database data type
func (JSON) GormDataType() string {
return "json"
}
// Use in model
type User struct {
gorm.Model
Metadata JSON
}// Check before dropping
if db.Migrator().HasTable(&OldModel{}) {
db.Migrator().DropTable(&OldModel{})
}
// Check before adding column
if !db.Migrator().HasColumn(&User{}, "Age") {
db.Migrator().AddColumn(&User{}, "Age")
}Keep track of migration versions in your application.
type Migration struct {
gorm.Model
Version string `gorm:"uniqueIndex"`
Applied time.Time
}
func migrate(db *gorm.DB) error {
// Create migration tracking table
db.AutoMigrate(&Migration{})
// Check if migration already applied
var count int64
db.Model(&Migration{}).Where("version = ?", "v1.0.0").Count(&count)
if count == 0 {
// Apply migration
if err := db.AutoMigrate(&User{}, &Order{}); err != nil {
return err
}
// Record migration
db.Create(&Migration{
Version: "v1.0.0",
Applied: time.Now(),
})
}
return nil
}Combine schema migration with data migration.
// Migrate schema
db.AutoMigrate(&User{})
// Migrate data
db.Model(&User{}).Where("old_status = ?", "pending").
Update("status", "active")if err := db.AutoMigrate(&User{}); err != nil {
log.Printf("Migration failed: %v", err)
// Handle error appropriately
}
m := db.Migrator()
// Check individual operations
if err := m.AddColumn(&User{}, "Age"); err != nil {
if !m.HasColumn(&User{}, "Age") {
// Column wasn't added
log.Printf("Failed to add column: %v", err)
}
}