Compose models from reusable structs using embedding, the embedded tag, and embeddedPrefix for column name prefixing.
GORM supports struct embedding, letting you define common fields once and share them across multiple models. Embedded fields are flattened into the parent table — there is no table inheritance.
The simplest form of embedding is using gorm.Model, which provides ID, CreatedAt, UpdatedAt, and DeletedAt:
type Model struct { ID uint `gorm:"primarykey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"`}type User struct { gorm.Model // embedded — fields are promoted into users table Name string Email string}
The resulting users table has columns: id, created_at, updated_at, deleted_at, name, email.
Anonymous (unnamed) struct embedding is detected automatically by GORM. Named struct fields require an explicit embedded tag:
// gorm.Model is embedded anonymously — GORM handles it automaticallytype User struct { gorm.Model Name string}
GORM’s embedding logic in schema/field.go:
// Anonymous fields, or fields with the EMBEDDED tag, are flattenedif _, ok := field.TagSettings["EMBEDDED"]; ok || (fieldStruct.Anonymous && ...) { // fields from the embedded schema are promoted into the parent schema for _, ef := range field.EmbeddedSchema.Fields { ef.Schema = schema // ... }}
When embedding a struct that shares field names with the parent, use embeddedPrefix to namespace the embedded columns:
type Address struct { Street string City string Zip string}type User struct { ID uint Name string HomeAddress Address `gorm:"embedded;embeddedPrefix:home_"` WorkAddress Address `gorm:"embedded;embeddedPrefix:work_"`}
Resulting columns:
Field
Column
HomeAddress.Street
home_street
HomeAddress.City
home_city
HomeAddress.Zip
home_zip
WorkAddress.Street
work_street
WorkAddress.City
work_city
WorkAddress.Zip
work_zip
The prefix is applied in schema/field.go:
if prefix, ok := field.TagSettings["EMBEDDEDPREFIX"]; ok && ef.DBName != "" { ef.DBName = prefix + ef.DBName}
All embedded fields land in the same table as the parent model. There is no concept of a separate base table. This differs from OOP-style inheritance — embedding is purely a column-sharing mechanism.
type Auditable struct { CreatedBy string `gorm:"size:100;not null"` UpdatedBy string `gorm:"size:100;not null"` CreatedAt time.Time UpdatedAt time.Time}type Invoice struct { ID uint `gorm:"primaryKey"` Number string `gorm:"size:50;uniqueIndex"` Amount float64 Auditable // embedded anonymously}type Payment struct { ID uint `gorm:"primaryKey"` InvoiceID uint Amount float64 Auditable // same struct, different table}
Invoice → invoices table, Payment → payments table. Both tables independently contain created_by, updated_by, created_at, and updated_at columns.
Because embedding copies field definitions into each model’s table, changes to the shared struct are reflected across all models that embed it after the next migration.
When a struct with a primary key field is embedded, the primary key tag is only honored when the field is explicitly tagged with primaryKey in the embedded struct itself. GORM’s parsing logic resets the primary key flag for embedded fields that do not carry the tag:
// From schema/field.goif ef.PrimaryKey { if !utils.CheckTruth(ef.TagSettings["PRIMARYKEY"], ef.TagSettings["PRIMARY_KEY"]) { ef.PrimaryKey = false // ... }}
This prevents accidentally inheriting primary key behavior from an embedded struct.
Embedding two structs that both define an ID field will cause a conflict. GORM resolves this by prioritizing the field with the shortest bind path (i.e., the one closest to the top-level struct).