Skip to main content

Basic create

Pass a pointer to a model struct to db.Create to insert a new row. GORM populates the primary key field on the struct after the insert.
type User struct {
  gorm.Model
  Name  string
  Email string
  Age   uint
}

user := User{Name: "Jinzhu", Email: "[email protected]", Age: 18}
result := db.Create(&user)

// user.ID is now populated with the inserted primary key
fmt.Println(user.ID)        // e.g. 1
fmt.Println(result.Error)   // nil on success
fmt.Println(result.RowsAffected) // 1
db.Create requires a pointer. Passing a value (non-pointer) will panic at runtime.

Create multiple records

Pass a slice pointer to insert multiple rows in a single statement.
users := []User{
  {Name: "Jinzhu", Email: "[email protected]", Age: 18},
  {Name: "Jackson", Email: "[email protected]", Age: 19},
  {Name: "Jill",    Email: "[email protected]",    Age: 20},
}

result := db.Create(&users)

for _, u := range users {
  fmt.Println(u.ID) // each ID is populated
}
fmt.Println(result.RowsAffected) // 3

Batch insert

Use CreateInBatches to split a large slice into smaller INSERT statements. This limits memory usage and avoids hitting database parameter limits.
var users []User
for i := 0; i < 10000; i++ {
  users = append(users, User{Name: fmt.Sprintf("user_%d", i)})
}

// Insert in batches of 100
db.CreateInBatches(users, 100)
You can also configure a default batch size globally so that db.Create uses it automatically:
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
  CreateBatchSize: 100,
})

// db.Create now inserts in batches of 100
db.Create(&users)
Batches larger than batchSize are wrapped in a transaction automatically. Set SkipDefaultTransaction: true on the config to opt out.

Select specific fields on create

Use Select to insert only the listed fields. All other fields are ignored.
user := User{Name: "Jinzhu", Email: "[email protected]", Age: 18}

// Only insert Name and Age; Email is omitted
db.Select("Name", "Age").Create(&user)
// INSERT INTO users (name, age, ...) VALUES ("Jinzhu", 18, ...)

Omit specific fields on create

Use Omit to exclude listed fields from the INSERT statement.
user := User{Name: "Jinzhu", Email: "[email protected]", Age: 18}

// Insert everything except Email
db.Omit("Email").Create(&user)
// INSERT INTO users (name, age, ...) VALUES ("Jinzhu", 18, ...)
You can also omit association fields by name:
// Omit the Languages many-to-many association
db.Omit("Languages.*").Create(&user)

// Omit the association and its join table entries entirely
db.Omit("Languages").Create(&user)

Save (upsert)

Save either inserts or updates a record depending on whether the primary key is set and non-zero.
// No primary key set → INSERT
user := User{Name: "Jinzhu", Age: 18}
db.Save(&user)
// INSERT INTO users (name, age, ...) VALUES (...)

// Primary key is set → UPDATE all fields
user.ID = 1
user.Name = "Jinzhu Updated"
db.Save(&user)
// UPDATE users SET name="Jinzhu Updated", age=18, updated_at=... WHERE id=1
Save performs a full-record update when the primary key is present. Every field — including zero-value fields — is written to the database. Use Updates when you only want to change specific columns.
When saving a slice, GORM uses an ON CONFLICT (id) DO UPDATE SET ... upsert:
users := []User{
  {Model: gorm.Model{ID: 1}, Name: "Updated Jinzhu"},
  {Name: "New User"},
}
db.Save(&users)

FirstOrCreate

FirstOrCreate fetches the first matching record, or creates it if none exists. The result is always populated.
var user User

// Get the first user named Jinzhu, or create one
result := db.FirstOrCreate(&user, User{Name: "Jinzhu"})

fmt.Println(user)              // User{Name: "Jinzhu", ...}
fmt.Println(result.RowsAffected) // 1 if created, 0 if found
Use Attrs to set extra fields only when creating:
var user User

// Email is only applied if the record is not found
db.Where(User{Name: "non_existing"}).
  Attrs(User{Email: "[email protected]"}).
  FirstOrCreate(&user)
// user → User{Name: "non_existing", Email: "[email protected]"}
// (new record created)

db.Where(User{Name: "Jinzhu"}).
  Attrs(User{Email: "[email protected]"}).
  FirstOrCreate(&user)
// user → User{Name: "Jinzhu", ...}
// (existing record returned, Attrs value ignored)
Use Assign to apply fields regardless of whether the record was found or created. When the record already exists, Assign triggers an UPDATE:
var user User

db.Where(User{Name: "Jinzhu"}).
  Assign(User{Email: "[email protected]"}).
  FirstOrCreate(&user)
// If found: UPDATE users SET email="[email protected]" WHERE id=...
// user → User{Name: "Jinzhu", Email: "[email protected]"}
// result.RowsAffected → 1

FirstOrInit

FirstOrInit is identical to FirstOrCreate except it never writes to the database. It either returns the found record or initializes an in-memory struct.
var user User

// Finds an existing user, or initializes (but does NOT save) a new one
db.FirstOrInit(&user, User{Name: "non_existing"})
// user → User{Name: "non_existing"}
// Nothing is written to the database

// Attrs: applied only if the record is not found
db.Where(User{Name: "non_existing"}).
  Attrs(User{Email: "[email protected]"}).
  FirstOrInit(&user)
// user → User{Name: "non_existing", Email: "[email protected]"}

// Assign: always applied
db.Where(User{Name: "Jinzhu"}).
  Assign(User{Email: "[email protected]"}).
  FirstOrInit(&user)
// user → User{Name: "Jinzhu", Age: 20, Email: "[email protected]"}
// (still not saved — call db.Save(&user) to persist)
FirstOrInit is useful when you want to prepare a struct for display or validation before deciding whether to save it.

Error handling

All GORM methods return *gorm.DB. Check .Error to detect failures:
result := db.Create(&user)
if result.Error != nil {
  // handle error
}

Build docs developers (and LLMs) love