# GORM User Guide

GORM is a full-featured, developer-friendly **Object-Relational Mapper (ORM) for Go**. It lets you work with your database using ordinary Go structs and method chains instead of writing raw SQL — while still giving you an escape hatch to raw SQL whenever you need it. GORM handles associations, transactions, migrations, hooks, soft deletes, prepared-statement caching, and SQL logging out of the box, and supports MySQL, PostgreSQL, SQLite, SQL Server, and other databases through pluggable drivers.

This guide shows you how to install GORM, define your models, and perform every common database operation, with copy-paste-runnable examples throughout.

> [!NOTE]
> GORM is the core ORM package (`gorm.io/gorm`). To actually talk to a database you also install a **driver** package (for example `gorm.io/driver/sqlite` or `gorm.io/driver/postgres`). This guide uses SQLite for runnable examples because it needs no server.

---

## 1. Quick Start

Here is a complete, runnable program that connects to a database, creates a table, and performs the basic Create / Read / Update / Delete cycle.

```go
package main

import (
	"fmt"

	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

// Product is a model. GORM maps it to a `products` table.
type Product struct {
	gorm.Model        // embeds ID, CreatedAt, UpdatedAt, DeletedAt
	Code       string
	Price      uint
}

func main() {
	// 1. Connect
	db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
	if err != nil {
		panic("failed to connect database")
	}

	// 2. Migrate the schema (creates/updates the `products` table)
	db.AutoMigrate(&Product{})

	// 3. Create
	db.Create(&Product{Code: "D42", Price: 100})

	// 4. Read
	var product Product
	db.First(&product, 1)                 // find product with integer primary key 1
	db.First(&product, "code = ?", "D42") // find product with code D42

	// 5. Update — update product's price to 200
	db.Model(&product).Update("Price", 200)
	// Update multiple fields
	db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // non-zero fields only
	db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})

	// 6. Delete — soft delete product (sets deleted_at)
	db.Delete(&product, 1)

	fmt.Println(product)
}
```

```text
{{1 2026-06-06 10:00:00 ... } F42 200}
```

> [!TIP]
> GORM returns the `*gorm.DB` from almost every method, so calls **chain**: `db.Where("price > ?", 100).Order("price desc").Limit(10).Find(&products)`. The chain is only executed when you call a *finisher* method such as `Find`, `First`, `Create`, `Save`, `Update`, `Delete`, `Count`, or `Scan`.

---

## 2. Installation

### 2.1 Requirements

| Requirement | Detail |
|-------------|--------|
| Go version | **1.18+** (the core module declares `go 1.18`; the generics API requires 1.18, and the `slog` logger requires 1.21+) |
| A database driver | One of the `gorm.io/driver/*` packages |
| A database | MySQL, PostgreSQL, SQLite, SQL Server, GaussDB, etc. (SQLite needs only a local file) |

### 2.2 Install the core package and a driver

=== "SQLite"

    ```bash
    go get -u gorm.io/gorm
    go get -u gorm.io/driver/sqlite
    ```

=== "PostgreSQL"

    ```bash
    go get -u gorm.io/gorm
    go get -u gorm.io/driver/postgres
    ```

=== "MySQL"

    ```bash
    go get -u gorm.io/gorm
    go get -u gorm.io/driver/mysql
    ```

=== "SQL Server"

    ```bash
    go get -u gorm.io/gorm
    go get -u gorm.io/driver/sqlserver
    ```

> [!NOTE]
> The SQLite driver (`gorm.io/driver/sqlite`) depends on `github.com/mattn/go-sqlite3`, which uses **cgo** — you need a C compiler installed to build it. The MySQL, PostgreSQL, and SQL Server drivers are pure Go.

### 2.3 Connecting

`gorm.Open` takes a **dialector** (created by your driver) and zero or more options. The most common option is `*gorm.Config`.

=== "SQLite"

    ```go
    import (
        "gorm.io/driver/sqlite"
        "gorm.io/gorm"
    )

    db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})
    ```

=== "PostgreSQL"

    ```go
    import (
        "gorm.io/driver/postgres"
        "gorm.io/gorm"
    )

    dsn := "host=localhost user=gorm password=gorm dbname=gorm port=5432 sslmode=disable"
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    ```

=== "MySQL"

    ```go
    import (
        "gorm.io/driver/mysql"
        "gorm.io/gorm"
    )

    dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    ```

> [!IMPORTANT]
> Always check the `err` returned by `gorm.Open`. On success, GORM by default **pings** the database to verify the connection (you can disable this with `DisableAutomaticPing: true` in the config).

> [!TIP]
> Reuse a single `*gorm.DB` for the lifetime of your application. It is safe for concurrent use and manages an underlying connection pool. To tune the pool, get the underlying `*sql.DB` and call its methods (see [Section 12](#12-connection-pooling)).

---

## 3. Defining Models

A **model** is a regular Go struct. GORM uses reflection and struct tags to map it to a table.

### 3.1 The conventions

By default GORM applies these conventions, so a plain struct usually needs no tags at all:

- The table name is the **snake_case, pluralized** struct name (`User` → `users`, `OrderItem` → `order_items`).
- Column names are the **snake_case** field names (`UserName` → `user_name`).
- A field named `ID` (any integer type) is the **primary key** and is auto-incremented.
- Fields named `CreatedAt` and `UpdatedAt` (of type `time.Time` or an integer) are automatically set on create/update.

```go
type User struct {
	ID        uint
	Name      string
	Email     string
	Age       uint8
	CreatedAt time.Time
	UpdatedAt time.Time
}
```

### 3.2 `gorm.Model`

GORM ships a convenience struct you can embed to get the four most common fields:

```go
// gorm.Model definition (from the source):
type Model struct {
	ID        uint `gorm:"primarykey"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt DeletedAt `gorm:"index"`
}
```

Embed it to add `ID`, `CreatedAt`, `UpdatedAt`, and a `DeletedAt` field that enables **soft deletes** (see [Section 9](#9-soft-deletes)):

```go
type User struct {
	gorm.Model
	Name string
}
```

### 3.3 Field tags

Control mapping with the `gorm:"..."` struct tag. Multiple settings are separated by `;`. Tag names are case-insensitive.

| Tag | Effect |
|-----|--------|
| `column` | Override the column name: `gorm:"column:user_name"` |
| `primaryKey` | Mark the field as a primary key |
| `autoIncrement` | Enable auto-increment |
| `autoIncrementIncrement` | Set the auto-increment step |
| `unique` | Add a unique constraint |
| `default` | Set a default value: `gorm:"default:18"` |
| `not null` | Add a NOT NULL constraint |
| `size` | Column size/length: `gorm:"size:255"` |
| `precision` / `scale` | Numeric precision and scale |
| `type` | Explicit column data type: `gorm:"type:varchar(100)"` |
| `comment` | Column comment |
| `index` | Create an index (`gorm:"index"` or named `gorm:"index:idx_name"`) |
| `uniqueIndex` | Create a unique index |
| `check` | Add a CHECK constraint: `gorm:"check:age > 0"` |
| `embedded` | Embed a struct's fields into this table |
| `embeddedPrefix` | Prefix embedded column names: `gorm:"embedded;embeddedPrefix:author_"` |
| `serializer` | Serialize the field: `gorm:"serializer:json"` (also `gob`, `unixtime`) |
| `autoCreateTime` | Set the field to the current time on create |
| `autoUpdateTime` | Set the field to the current time on update |
| `-` | Ignore the field entirely (`gorm:"-"`) |
| `->` | Read-only field (`gorm:"->"`); `<-` makes it write-only/creatable |

```go
type User struct {
	ID       uint   `gorm:"primaryKey"`
	Name     string `gorm:"size:128;not null"`
	Email    string `gorm:"uniqueIndex;size:255"`
	Age      uint8  `gorm:"default:18;check:age >= 0"`
	Bio      string `gorm:"type:text"`
	Settings JSON   `gorm:"serializer:json"`
	Secret   string `gorm:"-"` // never read or written
}
```

> [!TIP]
> Read/write **permissions** let you protect fields: `gorm:"->"` makes a field read-only (selected but never inserted/updated), `gorm:"<-:create"` makes it creatable but not updatable, and `gorm:"-"` ignores it completely.

### 3.4 Indexes

Indexes are declared on fields. Give several fields the **same index name** to build a composite index, and use `priority` to order the columns:

```go
type User struct {
	Name string `gorm:"index:idx_name_age,priority:1"`
	Age  int    `gorm:"index:idx_name_age,priority:2"`
}
```

A single `uniqueIndex` on one field also marks that field's `UniqueIndex` automatically. Supported index settings parsed from the tag include `CLASS` (e.g. `UNIQUE`, `FULLTEXT`, `SPATIAL`), `TYPE` (e.g. `btree`, `hash`, `gin`), `WHERE` (partial index), `COMMENT`, `EXPRESSION`, `SORT`, `COLLATE`, `LENGTH`, and `OPTION`.

### 3.5 Overriding the table name

Implement the `Tabler` interface to set a fixed table name, or use `db.Table("name")` per query:

```go
func (User) TableName() string {
	return "auth_users"
}
```

---

## 4. Creating Records

### 4.1 Insert a single record

```go
user := User{Name: "Jinzhu", Age: 18}

result := db.Create(&user) // pass a pointer

user.ID            // the primary key is back-filled after insert
result.Error       // returns error, if any
result.RowsAffected // number of rows inserted
```

> [!IMPORTANT]
> Always pass a **pointer** (`&user`) to `Create`. GORM writes the generated primary key (and other generated columns) back into the struct.

### 4.2 Insert specific fields only

```go
// Only insert name and age columns
db.Select("Name", "Age").Create(&user)

// Insert everything EXCEPT the listed columns
db.Omit("Age").Create(&user)
```

### 4.3 Batch insert

Pass a slice to `Create`, or use `CreateInBatches` to chunk large slices:

```go
users := []User{{Name: "a"}, {Name: "b"}, {Name: "c"}}
db.Create(&users)

// Insert in batches of 100
db.CreateInBatches(&users, 100)
```

You can also set a default batch size globally with `CreateBatchSize` in the config; `Create` will then automatically batch large slices.

### 4.4 Upsert / "on conflict"

Use the `clause.OnConflict` clause to control what happens when a row already exists:

```go
import "gorm.io/gorm/clause"

// Do nothing on conflict
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&users)

// Update specified columns on conflict (by primary key)
db.Clauses(clause.OnConflict{
	Columns:   []clause.Column{{Name: "id"}},
	DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
}).Create(&users)

// Update ALL columns (except primary keys) on conflict
db.Clauses(clause.OnConflict{UpdateAll: true}).Create(&users)
```

> [!NOTE]
> `db.Save()` on a slice automatically applies `OnConflict{UpdateAll: true}` for you (see [Section 6.4](#64-save)).

---

## 5. Querying Records

### 5.1 Retrieve one record

| Method | Behaviour |
|--------|-----------|
| `First(&user)` | First row **ordered by primary key**; errors with `ErrRecordNotFound` if none |
| `Take(&user)` | One row in **no particular order**; errors with `ErrRecordNotFound` if none |
| `Last(&user)` | Last row ordered by primary key (descending); errors with `ErrRecordNotFound` if none |

```go
var user User
db.First(&user)                  // SELECT * FROM users ORDER BY id LIMIT 1
db.Take(&user)                   // SELECT * FROM users LIMIT 1
db.Last(&user)                   // SELECT * FROM users ORDER BY id DESC LIMIT 1

db.First(&user, 10)              // by primary key: WHERE id = 10
db.First(&user, "name = ?", "x") // with a condition
```

> [!WARNING]
> `First`, `Take`, and `Last` return `gorm.ErrRecordNotFound` when nothing matches. Always check for it: `errors.Is(result.Error, gorm.ErrRecordNotFound)`. `Find` (below) does **not** raise this error for empty results.

### 5.2 Retrieve many records

```go
var users []User
result := db.Find(&users)          // SELECT * FROM users
result.RowsAffected                // number of rows returned

db.Find(&users, []int{1, 2, 3})    // WHERE id IN (1,2,3)
db.Where("age > ?", 18).Find(&users)
```

### 5.3 Conditions

`Where`, `Not`, and `Or` accept strings with `?` placeholders, structs, or maps. Conditions chain with **AND** by default.

```go
// String condition
db.Where("name = ?", "jinzhu").First(&user)
db.Where("age > ? AND name <> ?", 18, "admin").Find(&users)
db.Where("name IN ?", []string{"jinzhu", "jinzhu2"}).Find(&users)
db.Where("name LIKE ?", "%jin%").Find(&users)

// Struct condition (only non-zero fields are used)
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)

// Map condition (zero values ARE used)
db.Where(map[string]interface{}{"name": "jinzhu", "age": 0}).Find(&users)

// NOT and OR
db.Not("name = ?", "jinzhu").First(&user)
db.Where("name = ?", "jinzhu").Or("name = ?", "jinzhu2").Find(&users)
```

> [!CAUTION]
> When you query with a **struct**, GORM ignores **zero-value** fields (`0`, `""`, `false`). To filter on a zero value, use a map or a `Select` of the field names. This is the single most common GORM surprise.

### 5.4 Selecting, ordering, limiting

```go
db.Select("name", "age").Find(&users)        // only these columns
db.Select("COALESCE(age, ?)", 42).Find(&users)

db.Order("age desc, name").Find(&users)
db.Limit(10).Offset(20).Find(&users)         // pagination
db.Distinct("name", "age").Find(&users)
db.Group("name").Having("count(id) > ?", 1).Find(&users)
```

> [!TIP]
> Cancel a previously chained `Limit` or `Offset` by passing `-1`: `db.Limit(10).Find(&a).Limit(-1).Find(&b)`.

### 5.5 Pluck and Count

```go
// Pluck a single column into a slice
var names []string
db.Model(&User{}).Pluck("name", &names)

// Count rows
var count int64
db.Model(&User{}).Where("age > ?", 18).Count(&count)
```

### 5.6 Scan into a custom struct

`Scan` reads query results into any struct — handy for aggregates and joins that don't match a model:

```go
type Result struct {
	Name  string
	Total int
}

var res []Result
db.Model(&User{}).
	Select("name, sum(age) as total").
	Group("name").
	Scan(&res)
```

### 5.7 Iterating with `Rows` and `FindInBatches`

For very large result sets, stream rows or process them in batches:

```go
// Process in chunks of 100 — efficient for large tables
db.Where("age > ?", 0).FindInBatches(&users, 100, func(tx *gorm.DB, batch int) error {
	for _, u := range users {
		// ... handle each user
	}
	return nil // returning an error stops processing
})

// Or iterate raw rows
rows, _ := db.Model(&User{}).Rows()
defer rows.Close()
for rows.Next() {
	var u User
	db.ScanRows(rows, &u)
}
```

### 5.8 FirstOrInit / FirstOrCreate

Find a record, or initialize / create one if it doesn't exist.

```go
// FirstOrInit: find or build (in memory only, no INSERT)
db.Where(User{Name: "non_existing"}).FirstOrInit(&user)

// FirstOrCreate: find or INSERT
db.Where(User{Name: "jinzhu"}).FirstOrCreate(&user)

// Attrs: extra attributes used ONLY when the record is not found
db.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)

// Assign: attributes applied whether found or not (causes an UPDATE if found)
db.Where(User{Name: "jinzhu"}).Assign(User{Age: 30}).FirstOrCreate(&user)
```

---

## 6. Updating Records

### 6.1 Update a single column

```go
db.Model(&user).Update("name", "hello")

// With a condition (no primary key in the model):
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
```

### 6.2 Update multiple columns

`Updates` accepts a struct (only **non-zero** fields are written) or a map (all keys are written):

```go
// Struct — updates only non-zero fields (name, age)
db.Model(&user).Updates(User{Name: "hello", Age: 18})

// Map — updates exactly these columns, including zero values
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "active": false})
```

> [!CAUTION]
> Because `Updates` with a **struct** skips zero-value fields, `db.Model(&user).Updates(User{Active: false})` will **not** set `active` to false. Use a `map`, or `Select` the columns explicitly, to update zero values.

### 6.3 Select / Omit on update

```go
// Update only the selected fields
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 0})

// Update all fields EXCEPT the omitted ones
db.Model(&user).Omit("name").Updates(User{Name: "x", Age: 18})

// Select("*") forces ALL fields (including zero values) from a struct
db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Age: 0})
```

### 6.4 Save

`Save` upserts the whole record. If the primary key is set it updates **all** fields; otherwise it inserts:

```go
db.Save(&user) // INSERT if no PK, otherwise UPDATE every column
```

### 6.5 Update without hooks: `UpdateColumn(s)`

`UpdateColumn` and `UpdateColumns` write directly, **skipping hooks** and the `updated_at` auto-timestamp:

```go
db.Model(&user).UpdateColumn("name", "hello")
db.Model(&user).UpdateColumns(map[string]interface{}{"name": "hello", "age": 18})
```

> [!WARNING]
> A "global" update or delete — one with no `WHERE` condition and no primary key — is **blocked** by default and returns `ErrMissingWhereClause`, to protect you from accidentally changing every row. To intentionally update all rows, enable `AllowGlobalUpdate` via a session: `db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "x")`.

### 6.6 Expressions

Use `gorm.Expr` to update with a SQL expression rather than a literal:

```go
db.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
// UPDATE products SET price = price * 2 + 100 WHERE ...
```

---

## 7. Deleting Records

```go
// Delete by model (uses its primary key)
db.Delete(&user)

// Delete by condition
db.Where("age < ?", 18).Delete(&User{})

// Delete by primary key inline
db.Delete(&User{}, 10)
db.Delete(&User{}, []int{1, 2, 3})
```

> [!WARNING]
> Like updates, a delete with no condition is blocked with `ErrMissingWhereClause`. Provide a primary key or a `Where` clause.

If your model has a `gorm.DeletedAt` field (for example via `gorm.Model`), `Delete` performs a **soft delete** — see the next section.

---

## 8. Associations

GORM models relationships between structs as **Belongs To**, **Has One**, **Has Many**, and **Many To Many**.

```go
type User struct {
	gorm.Model
	Name       string
	CompanyID  uint
	Company    Company         // Belongs To
	CreditCard CreditCard      // Has One
	Orders     []Order         // Has Many
	Languages  []Language `gorm:"many2many:user_languages;"` // Many To Many
}
```

### 8.1 Creating with associations

By default, creating or saving a record also creates its associated records (upsert), so you can build an object graph in one call:

```go
db.Create(&User{
	Name:    "jinzhu",
	Orders:  []Order{{Price: 10}, {Price: 20}},
	Company: Company{Name: "Acme"},
})
```

Skip auto-creating associations with `Omit`:

```go
db.Omit("Orders").Create(&user)                // skip the Orders association
db.Omit(clause.Associations).Create(&user)     // skip ALL associations
```

### 8.2 Preloading (eager loading)

`Preload` loads associations in separate queries to avoid the N+1 problem:

```go
// Load every user together with their orders and company
db.Preload("Orders").Preload("Company").Find(&users)

// Nested preload
db.Preload("Orders.OrderItems").Find(&users)

// Conditional preload
db.Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
```

### 8.3 Joins

`Joins` performs a SQL JOIN. For a Belongs To / Has One relation you can join by association name to load it in a single query:

```go
db.Joins("Company").Find(&users) // LEFT JOIN companies ...

// Raw join with a condition
db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "x@y.z").Find(&users)

// INNER JOIN variant
db.InnerJoins("Company").Find(&users)
```

### 8.4 Association mode

`db.Model(&user).Association("Name")` gives you fine-grained control over a single relationship:

```go
// Find associated records
var orders []Order
db.Model(&user).Association("Orders").Find(&orders)

// Append / Replace / Delete / Clear
db.Model(&user).Association("Orders").Append(&Order{Price: 100})
db.Model(&user).Association("Languages").Replace([]Language{en, fr})
db.Model(&user).Association("Orders").Delete(&order1)
db.Model(&user).Association("Orders").Clear()

// Count associated records
count := db.Model(&user).Association("Orders").Count()
```

> [!NOTE]
> By default, association writes only save the foreign-key reference, not the full associated record. Set `FullSaveAssociations: true` (in config or via a session) to also upsert the associated records' own fields.

---

## 9. Soft Deletes

If a model includes a field of type `gorm.DeletedAt` (which `gorm.Model` provides), GORM **never physically deletes** the row. Instead, `Delete` sets `deleted_at` to the current time, and every subsequent query automatically adds `WHERE deleted_at IS NULL`.

```go
type User struct {
	gorm.Model // includes DeletedAt
	Name string
}

db.Delete(&user)        // UPDATE users SET deleted_at = NOW() WHERE id = ...
db.First(&user, 1)      // automatically excludes soft-deleted rows
```

### 9.1 Querying soft-deleted records

Use `Unscoped` to bypass the soft-delete filter:

```go
// Include soft-deleted rows in a query
db.Unscoped().Where("age = ?", 20).Find(&users)

// Permanently delete (real DELETE) a soft-deletable row
db.Unscoped().Delete(&user)
```

> [!TIP]
> You can customize the "not deleted" sentinel with the `zeroValue` tag, e.g. `gorm:"zeroValue:1970-01-01 00:00:01"`, when your column is `NOT NULL` and cannot store SQL `NULL`.

---

## 10. Transactions

### 10.1 The `Transaction` helper (recommended)

Run a function inside a transaction. Returning an error rolls back; returning `nil` commits. A panic also rolls back automatically.

```go
err := db.Transaction(func(tx *gorm.DB) error {
	if err := tx.Create(&User{Name: "a"}).Error; err != nil {
		return err // rolls back
	}
	if err := tx.Create(&Order{Price: 10}).Error; err != nil {
		return err // rolls back
	}
	return nil // commits
})
```

> [!IMPORTANT]
> Inside the closure, use the provided `tx`, **not** the outer `db`. Operations on `db` run outside the transaction.

### 10.2 Manual transactions

```go
tx := db.Begin()
if err := tx.Create(&user).Error; err != nil {
	tx.Rollback()
	return
}
tx.Commit()
```

### 10.3 Nested transactions and save points

GORM supports nested transactions using save points, plus explicit `SavePoint` / `RollbackTo`:

```go
tx := db.Begin()
tx.Create(&user1)

tx.SavePoint("sp1")
tx.Create(&user2)
tx.RollbackTo("sp1") // undoes user2 but keeps user1

tx.Commit()
```

> [!NOTE]
> By default GORM wraps every single `Create`, `Update`, and `Delete` in its own transaction for data integrity. If you are doing many writes and want more speed, set `SkipDefaultTransaction: true` in the config (or per session) to turn this off.

---

## 11. Raw SQL

When the query builder isn't enough, drop to raw SQL.

```go
// Raw query scanned into a struct/slice
var result Result
db.Raw("SELECT name, age FROM users WHERE id = ?", 3).Scan(&result)

// Named parameters
db.Raw("SELECT * FROM users WHERE name = @name OR email = @name",
	sql.Named("name", "jinzhu")).Scan(&users)

// Exec for statements that don't return rows
db.Exec("UPDATE users SET age = age + 1 WHERE id = ?", 10)
```

### 11.1 Inspect generated SQL with `ToSQL` and `DryRun`

```go
sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
	return tx.Model(&User{}).Where("id = ?", 100).Limit(10).Order("age desc").Find(&[]User{})
})
// sql == "SELECT * FROM `users` WHERE id = 100 ORDER BY age desc LIMIT 10"
```

> [!CAUTION]
> The SQL produced by `ToSQL`/the logger has parameters interpolated for readability and is **not safe to execute directly** — running it can introduce SQL injection. Use it only for inspection and debugging.

---

## 12. Connection Pooling

GORM uses Go's standard `database/sql` pool. Get the underlying `*sql.DB` to tune it:

```go
sqlDB, err := db.DB()

sqlDB.SetMaxIdleConns(10)            // idle connections in the pool
sqlDB.SetMaxOpenConns(100)           // max open connections
sqlDB.SetConnMaxLifetime(time.Hour)  // recycle connections after an hour

// Health check
sqlDB.Ping()

// Close (rarely needed; usually you keep the pool for the app's lifetime)
sqlDB.Close()
```

---

## 13. Sessions, Context, and Debugging

A **session** produces a new `*gorm.DB` with overridden settings, without mutating the original.

```go
// Per-request context (for cancellation / deadlines)
db.WithContext(ctx).Find(&users)

// One-off settings
tx := db.Session(&gorm.Session{
	PrepareStmt:          true,  // cache prepared statements for this session
	SkipDefaultTransaction: true,
	FullSaveAssociations: true,
})

// Print the SQL of a single call
db.Debug().Where("name = ?", "jinzhu").First(&user)
```

> [!TIP]
> `db.Debug()` switches the logger to `Info` level for one chain so you can see the exact SQL and timing in your console — invaluable when a query behaves unexpectedly.

---

## 14. Hooks (Lifecycle Callbacks)

Implement the relevant method on your model to run code before/after an operation. Each hook receives the active `*gorm.DB` and returns an `error` (returning an error aborts the operation and rolls back the surrounding transaction).

| Hook | When it runs |
|------|--------------|
| `BeforeCreate(tx *gorm.DB) error` | Before INSERT |
| `AfterCreate(tx *gorm.DB) error` | After INSERT |
| `BeforeUpdate(tx *gorm.DB) error` | Before UPDATE |
| `AfterUpdate(tx *gorm.DB) error` | After UPDATE |
| `BeforeSave(tx *gorm.DB) error` | Before any INSERT or UPDATE |
| `AfterSave(tx *gorm.DB) error` | After any INSERT or UPDATE |
| `BeforeDelete(tx *gorm.DB) error` | Before DELETE |
| `AfterDelete(tx *gorm.DB) error` | After DELETE |
| `AfterFind(tx *gorm.DB) error` | After a row is loaded by a query |

```go
func (u *User) BeforeCreate(tx *gorm.DB) error {
	if u.Name == "admin" {
		return errors.New("cannot create reserved user")
	}
	u.UUID = uuid.New().String()
	return nil
}
```

> [!NOTE]
> Hooks do not run for `UpdateColumn(s)` or when a session has `SkipHooks: true`.

---

## 15. Error Handling

Every finisher sets `db.Error`. GORM exports sentinel errors you can compare with `errors.Is`.

```go
result := db.First(&user, 100)
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
	// no user with id 100
}
```

Commonly used sentinel errors:

| Error | Meaning |
|-------|---------|
| `gorm.ErrRecordNotFound` | `First`/`Take`/`Last` found no row |
| `gorm.ErrDuplicatedKey` | Unique-key constraint violation |
| `gorm.ErrForeignKeyViolated` | Foreign-key constraint violation |
| `gorm.ErrCheckConstraintViolated` | CHECK constraint violation |
| `gorm.ErrMissingWhereClause` | A global update/delete was blocked |
| `gorm.ErrInvalidTransaction` | `Commit`/`Rollback` with no active transaction |
| `gorm.ErrPrimaryKeyRequired` | An operation needed a primary key that was missing |
| `gorm.ErrInvalidValue` | Destination is not a pointer to a struct or slice |
| `gorm.ErrEmptySlice` | Tried to create from an empty slice |

> [!IMPORTANT]
> `gorm.ErrDuplicatedKey`, `gorm.ErrForeignKeyViolated`, and `gorm.ErrCheckConstraintViolated` are only returned when error translation is enabled with `TranslateError: true` in the config **and** your driver supports it. Without it, you receive the raw driver error.

---

## 16. The Generics API (Go 1.18+)

In addition to the classic chainable API, GORM offers a **type-safe generics API** via `gorm.G[T]`, which carries a `context.Context` on every finisher and returns typed values instead of writing into a pointer.

```go
import "context"

ctx := context.Background()

// Create
user := User{Name: "jinzhu"}
err := gorm.G[User](db).Create(ctx, &user)

// Query — returns a typed value, plus error
u, err := gorm.G[User](db).Where("id = ?", 1).First(ctx)
users, err := gorm.G[User](db).Where("age > ?", 18).Find(ctx)

// Update / Delete return rows affected
rows, err := gorm.G[User](db).Where("id = ?", 1).Update(ctx, "name", "new")
rows, err = gorm.G[User](db).Where("age < ?", 18).Delete(ctx)

// Capture sql.Result + RowsAffected with WithResult()
result := gorm.WithResult()
err = gorm.G[User](db, result).Create(ctx, &user)
_ = result.RowsAffected
```

The generics builder supports the same chain methods you already know — `Where`, `Not`, `Or`, `Limit`, `Offset`, `Select`, `Omit`, `Order`, `Group`, `Having`, `Distinct`, `Joins`, `Preload`, `Scopes`, `Table`, and `Raw`/`Exec`.

> [!TIP]
> Use the generics API when you want compile-time type safety and explicit context propagation. Use the classic API for its broader ecosystem of examples and plugins. They share the same engine and can be mixed in one program.

---

## 17. Reusable Query Logic with Scopes

A **scope** is a function that takes and returns a `*gorm.DB`. Scopes let you package and reuse query fragments:

```go
func ActiveUsers(db *gorm.DB) *gorm.DB {
	return db.Where("active = ?", true)
}

func OlderThan(age int) func(*gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		return db.Where("age > ?", age)
	}
}

db.Scopes(ActiveUsers, OlderThan(18)).Find(&users)
```

---

## 18. Troubleshooting

### Error: `record not found`

**Cause:** `First`, `Take`, or `Last` matched no rows. GORM returns `gorm.ErrRecordNotFound`.
**Solution:** Check explicitly and treat it as a normal "missing" case, not a failure:

```go
if err := db.First(&user, id).Error; errors.Is(err, gorm.ErrRecordNotFound) {
	// handle "not found"
}
```

**Prevention:** Use `Find` (which returns an empty slice and no error) when zero results are acceptable, and check `RowsAffected`.

### My struct condition / update silently ignores some fields

**Cause:** When you pass a **struct** to `Where` or `Updates`, GORM skips zero-value fields (`0`, `""`, `false`, `nil`).
**Solution:** Use a `map[string]interface{}`, or `Select` the column names explicitly.
**Prevention:** Reserve structs for non-zero filters; use maps whenever a zero value is meaningful.

### Error: `WHERE conditions required`

**Cause:** You issued an `Update` or `Delete` with no primary key and no `Where` clause — GORM blocks "global" writes (`ErrMissingWhereClause`).
**Solution:** Add a condition, or, if you truly mean to affect all rows, enable global updates: `db.Session(&gorm.Session{AllowGlobalUpdate: true})`.

### How do I detect a duplicate-key error? *(community: issue #4037)*

**Cause:** Each database reports unique-constraint violations differently, so the raw error string varies.
**Solution:** Enable `TranslateError: true` in `gorm.Config` and compare against `gorm.ErrDuplicatedKey`:

```go
db, _ := gorm.Open(dialector, &gorm.Config{TranslateError: true})
if errors.Is(db.Create(&user).Error, gorm.ErrDuplicatedKey) { /* ... */ }
```

### How do I do global / centralized error handling? *(community: issue #7158)*

**Solution:** Register a custom callback or wrap operations in a helper that inspects `db.Error` after each call. GORM does not provide a single global error handler; the idiomatic approach is to check `result.Error` (or use the generics API which returns `error` directly) and centralize handling in your repository layer.

### `Joins` on two unrelated tables produces no rows / a bad query *(community: issue #7745)*

**Cause:** Joining by **association name** (`Joins("Orders")`) only works when the two models actually have a defined relationship. Without one, GORM cannot infer the join condition.
**Solution:** Use a raw join with an explicit `ON` clause: `db.Joins("JOIN orders ON orders.user_id = users.id").Find(&users)`.

### SQLite build fails with a C compiler error

**Cause:** `gorm.io/driver/sqlite` relies on cgo (`mattn/go-sqlite3`).
**Solution:** Install a C toolchain (e.g. GCC / Xcode command-line tools) and ensure `CGO_ENABLED=1`. Alternatively use a pure-Go database like PostgreSQL or MySQL for development.

### Created record has `ID == 0` after `Create`

**Cause:** You passed a value instead of a pointer.
**Solution:** Always pass a pointer: `db.Create(&user)`, not `db.Create(user)`.

---

## 19. FAQ

**Q: What databases does GORM support?**
GORM is database-agnostic via driver packages: MySQL, PostgreSQL, SQLite, SQL Server, and GaussDB are maintained under `gorm.io/driver/*`, and the community maintains others. You pick the driver at `gorm.Open` time.

**Q: Do I have to write any SQL?**
No, the query builder covers the vast majority of needs. But you can always drop to `Raw`/`Exec` for arbitrary SQL, and inspect generated SQL with `ToSQL` or `Debug`.

**Q: How do I see the SQL GORM generates?**
Call `db.Debug()` for a single chain, set the logger to `Info` level globally, or use `db.ToSQL(func...)` to capture the statement as a string.

**Q: Is a `*gorm.DB` safe to share across goroutines?**
Yes. Create it once at startup and reuse it; it manages a connection pool internally. Per-request state (context, conditions) is isolated via method chaining and sessions.

**Q: Why didn't my `Updates(User{Active: false})` change anything?**
Struct updates skip zero values. Use a map (`Updates(map[string]interface{}{"active": false})`) or `Select("active")`.

**Q: How do I update or delete every row on purpose?**
GORM blocks unconditioned writes. Opt in with a session: `db.Session(&gorm.Session{AllowGlobalUpdate: true})`.

**Q: What's the difference between `Save`, `Update`, and `Updates`?**
`Update` sets one column; `Updates` sets several (struct = non-zero fields, map = exact columns); `Save` upserts the entire record (insert if no primary key, otherwise update all columns).

**Q: What's the difference between `First` and `Find`?**
`First` fetches one row ordered by primary key and returns `ErrRecordNotFound` when empty; `Find` fetches into a slice (or struct) and returns no error for empty results.

**Q: How do I improve performance for repeated queries?**
Enable prepared-statement caching with `PrepareStmt: true` (in config or a session). GORM caches and reuses prepared statements with an LRU + TTL.

**Q: How do I permanently delete a soft-deletable record?**
Use `db.Unscoped().Delete(&record)`.

**Q: Should I use the classic API or the generics API?**
Both are fully supported and share the same engine. Choose the generics API (`gorm.G[T]`) for compile-time type safety and explicit `context`; choose the classic chainable API for the larger body of examples and plugins. You can mix them.

---

_Generated June 2026 from go-gorm/gorm at d0ee5e2. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
