GORM — API Surface#
API types#
Library — GORM has no binary, no server, no HTTP endpoints, no gRPC services, no CLI. Its entire surface is a Go library API consumed by user code importing gorm.io/gorm.
Library API#
Public packages exported for use as a library#
| Package | Role |
|---|---|
gorm.io/gorm (root) | Core library: DB, Config, Session, chainable/finisher methods, plugin/callback hooks |
gorm.io/gorm/clause | Typed SQL AST expressions usable directly: Where, Or, And, Not, Select, OrderBy, Limit, OnConflict, etc. |
gorm.io/gorm/schema | Schema reflection types and serializer registry (advanced/plugin use) |
gorm.io/gorm/logger | Logger interface + default and slog-based implementations |
gorm.io/gorm/migrator | CommonMigrator base for dialect migration implementations |
gorm.io/gorm/callbacks | RegisterDefaultCallbacks() called by dialects; individual callback fns (BeforeCreate, Query, etc.) are public for override use |
API style#
Fluent method chaining on *DB. All chainable and finisher methods return *DB. Errors are accumulated on db.Error rather than returned from each call. Finisher methods trigger execution; chainable methods are lazy.
// typical usage pattern
var users []User
result := db.Where("age > ?", 18).Order("name").Limit(10).Find(&users)
if result.Error != nil { ... }
// result.RowsAffected is also availableEntry point#
func Open(dialector Dialector, opts ...Option) (*DB, error)gorm.Open is the single constructor. Dialector is supplied by an external driver package (e.g., gorm.io/driver/postgres). opts accepts *Config or anything implementing the Option interface (Apply(*Config) error + AfterInitialize(*DB) error).
Chainable API (chainable_api.go)#
Methods that add constraints/clauses to the current statement; each returns a new *DB (copy-on-write semantics via clone field):
| Method | Purpose |
|---|---|
Model(value) | Set model type / table inference |
Table(name, args...) | Explicit table name (supports subqueries) |
Distinct(args...) | Add DISTINCT |
Select(query, args...) | Column selection |
Omit(columns...) | Exclude columns from SELECT/INSERT |
MapColumns(m) | Remap struct field names to column names |
Where(query, args...) | Add WHERE condition (string, struct, or map) |
Not(query, args...) | Add NOT WHERE condition |
Or(query, args...) | Add OR WHERE condition |
Joins(query, args...) | LEFT JOIN (string-based) |
InnerJoins(query, args...) | INNER JOIN (string-based) |
Group(name) | GROUP BY |
Having(query, args...) | HAVING |
Order(value) | ORDER BY |
Limit(limit) | LIMIT |
Offset(offset) | OFFSET |
Scopes(funcs...) | Reusable query fragments (func(*DB) *DB) |
Preload(query, args...) | Eager-load associations |
Attrs(attrs...) | Default attributes for FirstOrInit/FirstOrCreate |
Assign(attrs...) | Always-assign attributes for FirstOrInit/FirstOrCreate |
Unscoped() | Disable soft-delete filtering |
Raw(sql, values...) | Raw SQL query (enters raw mode) |
Clauses(conds...) | Inject typed clause.Expression objects directly |
Finisher API (finisher_api.go)#
Methods that execute a database operation:
Create
| Method | Description |
|---|---|
Create(value) | INSERT single record or slice |
CreateInBatches(value, batchSize) | INSERT in batches |
Save(value) | Upsert (CREATE or full UPDATE based on primary key) |
Query
| Method | Description |
|---|---|
First(dest, conds...) | SELECT ORDER BY pk ASC LIMIT 1; returns ErrRecordNotFound if absent |
Take(dest, conds...) | SELECT LIMIT 1 (no ordering) |
Last(dest, conds...) | SELECT ORDER BY pk DESC LIMIT 1 |
Find(dest, conds...) | SELECT all matching records |
FindInBatches(dest, batchSize, fc) | Cursor-style batch retrieval |
FirstOrInit(dest, conds...) | First or initialise (no save) |
FirstOrCreate(dest, conds...) | First or INSERT |
Pluck(column, dest) | SELECT single column into slice |
Count(count *int64) | SELECT COUNT |
Scan(dest) | Scan arbitrary results (does not set model) |
ScanRows(rows, dest) | Scan *sql.Rows manually |
Row() | Returns raw *sql.Row |
Rows() | Returns raw *sql.Rows |
Update
| Method | Description |
|---|---|
Update(column, value) | UPDATE single column |
Updates(values) | UPDATE multiple columns (struct or map) |
UpdateColumn(column, value) | UPDATE without hooks/time auto-fill |
UpdateColumns(values) | UPDATE multiple columns without hooks |
Delete
| Method | Description |
|---|---|
Delete(value, conds...) | DELETE (soft-delete if model has DeletedAt) |
Raw / Exec
| Method | Description |
|---|---|
Exec(sql, values...) | Raw SQL DML execution |
Transactions
| Method | Description |
|---|---|
Transaction(fc, opts...) | Block-scoped transaction with automatic commit/rollback |
Begin(opts...) | Manual BEGIN |
Commit() | Manual COMMIT |
Rollback() | Manual ROLLBACK |
SavePoint(name) | SAVEPOINT |
RollbackTo(name) | ROLLBACK TO SAVEPOINT |
Connection(fc) | Dedicated connection scope |
Session / utility methods (gorm.go)#
| Method | Description |
|---|---|
Session(config *Session) | Clone DB with per-session overrides (DryRun, PrepareStmt, SkipHooks, etc.) |
WithContext(ctx) | Sugar over Session(&Session{Context: ctx}) |
Debug() | Sugar over Session(&Session{Logger: info-level logger}) |
Set(key, value) | Store arbitrary value on Statement (visible to callbacks) |
Get(key) | Retrieve value from Statement |
InstanceSet(key, value) | Store value on DB instance (shared across clones) |
InstanceGet(key) | Retrieve instance-level value |
AddError(err) | Append an error to db.Error |
DB() | Unwrap underlying *sql.DB |
ToSQL(queryFn) | DryRun helper — return SQL string without executing |
SetupJoinTable(model, field, joinTable) | Register a custom join table model |
Use(plugin) | Register and initialize a Plugin |
Callback() | Return *callbacks for hook registration |
Association API (association.go)#
Accessed via db.Association("FieldName"):
| Method | Description |
|---|---|
Find(out, conds...) | Query associated records |
Append(values...) | Append to association (adds FK / join row) |
Replace(values...) | Replace all association records |
Delete(values...) | Delete from association |
Clear() | Remove all associations |
Count() | Count associated records |
Unscoped() | Disable soft-delete scoping |
Migration API (Migrator interface, migrator.go:68)#
Accessed via db.Migrator(). The dialect implements this interface; db.AutoMigrate(dst...) is the common shortcut.
Key method groups:
- Tables:
CreateTable,DropTable,HasTable,RenameTable,GetTables,TableType - Columns:
AddColumn,DropColumn,AlterColumn,MigrateColumn,HasColumn,RenameColumn,ColumnTypes - Indexes:
CreateIndex,DropIndex,HasIndex,RenameIndex,GetIndexes - Constraints:
CreateConstraint,DropConstraint,HasConstraint - Views:
CreateView,DropView - Misc:
CurrentDatabase,FullDataTypeOf,GetTypeAliases
Callback / hook registration API (callbacks.go)#
db.Callback() returns a *callbacks exposing six per-operation *processor objects:
db.Callback().Create() → *processor
db.Callback().Query() → *processor
db.Callback().Update() → *processor
db.Callback().Delete() → *processor
db.Callback().Row() → *processor
db.Callback().Raw() → *processorEach *processor exposes a fluent hook-registration API:
db.Callback().Create().
Before("gorm:create").
Register("myapp:hook", func(db *gorm.DB) { ... })| Method | Description |
|---|---|
Before(name) | Constrain new callback to run before named callback |
After(name) | Constrain new callback to run after named callback |
Match(fc) | Conditional callback (predicate on *DB) |
Register(name, fn) | Add a new named callback function |
Remove(name) | Remove a callback by name |
Replace(name, fn) | Replace an existing callback |
Get(name) | Retrieve a callback function by name |
Execute(db) | Run the compiled pipeline (called internally by finishers) |
Model lifecycle hooks (BeforeCreate, AfterCreate, BeforeUpdate, etc.) are ordinary methods on the model struct — they are detected via schema/ reflection and registered as callbacks during RegisterDefaultCallbacks.
Plugin API (interfaces.go)#
type Plugin interface {
Name() string
Initialize(*DB) error
}Registered via db.Use(plugin). Stored in Config.Plugins map keyed by Name(). Initialize(*DB) is called once; plugins typically register callbacks or store state via db.InstanceSet.
Generics API (generics.go) — Go 1.18+#
G[T any](db *DB, opts ...clause.Expression) Interface[T] — a type-safe fluent wrapper around *DB:
userDB := gorm.G[User](db)
users, err := userDB.Where("age > ?", 18).Find(ctx) // returns ([]User, error)
user, err := userDB.Where("id = ?", 1).First(ctx) // returns (User, error)
err = userDB.Create(ctx, &User{Name: "Alice"})Key interfaces returned:
| Interface | Role |
|---|---|
Interface[T] | Root — Raw, Exec, and embeds CreateInterface[T] |
CreateInterface[T] | Chainable + finisher for create context |
ChainInterface[T] | Chainable + finisher for query/update/delete context |
ExecInterface[T] | Finisher methods: First, Last, Take, Find, FindInBatches, Row, Rows, Scan |
JoinBuilder | Type-safe join condition builder |
PreloadBuilder | Type-safe preload condition builder (includes LimitPerRecord) |
The generics layer is entirely additive — it wraps the *DB API and returns typed results and errors instead of *DB. It does not change the underlying execution path.
API style summary#
| Dimension | Approach |
|---|---|
| Primary style | Fluent method chaining on *DB; returns *DB from all methods |
| Error handling | Accumulated on db.Error (check after finisher, not after each call) |
| Copy-on-write safety | Each chain clones the Statement — shared *DB is safe to reuse |
| Configuration | *Config struct + Option interface for pre- and post-init hooks |
| Per-query overrides | db.Session(&Session{...}) or db.WithContext(ctx) |
| Type safety | interface{} / any for legacy API; generics via G[T] for new code |
| Backward compatibility | Additive only — no method removals; generics API is a new surface layer |
| Extensibility entry points | Callback() (pipeline hooks), Use() (plugins), Dialector interface |
Notable API design observations#
Error sink pattern. All finishers return
*DBrather than(*DB, error). Callers checkresult.Errorafter the call. This enables chaining through errors (db.Where(...).Find(...)can be written in one expression) but departs from idiomatic Go error handling. The genericsG[T]API fixes this: all finishers return(T, error).Dual entry points for the same operation.
db.Where("id = ?", 1)(string),db.Where(&User{ID: 1})(struct), anddb.Where(map[string]interface{}{"id": 1})(map) all work. This polymorphism is handled byclause/AST construction internally, but makes the API surface unusually permissive.ToSQLfor testability.db.ToSQL(func(tx *DB) *DB { return tx.Find(&users) })returns the generated SQL string without executing — a clean API for testing query construction.Callbacks as the public extension mechanism. The hook system (
db.Callback().Create().Before(...).Register(...)) is the documented, stable way for plugins and user code to extend behavior. This makes the plugin API composable and avoids inheritance.clause.Expressionas an escape hatch. Users can inject rawclause.Expressionobjects viadb.Clauses(...), giving full access to the SQL AST without abandoning the ORM. This is commonly used for database-specific features (ON CONFLICT, RETURNING, FOR UPDATE).