# GORM ships no database driver, and its API is split across two files

> GORM is a Go ORM whose core module has three direct dependencies and none of them is a SQL driver, so a dialect is always a second module you add. Its API shape is visible in the filenames, chainable_api.go against finisher_api.go, and its extension points all sit on the same callback chain.

**go-gorm/gorm** — The fantastic ORM library for Golang, aims to be developer friendly

- Repository: https://github.com/go-gorm/gorm
- Website: https://gorm.io
- Stars: 39,972 · Forks: 4,192
- Language: Go
- License: MIT
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/go-gorm-gorm

## The core module has three dependencies and no driver

The whole dependency list of the ORM is this:

```
module gorm.io/gorm

go 1.18

require (
	github.com/jinzhu/inflection v1.0.0
	github.com/jinzhu/now v1.1.5
	golang.org/x/text v0.20.0
)
```

Three direct requires for an ORM is unusual, and the shape of them explains the design. `jinzhu/inflection` is a pluraliser, `jinzhu/now` is a time helper, and `x/text` handles text processing. There is no database driver in that list, and there is no dialect package in the repository either, since the root has no `dialects/` directory.

So the core gives you query construction, schema parsing, hooks, migrations and the plugin API, and it cannot open a connection on its own. A dialect arrives as a separate module under `gorm.io/driver/`, and the module's own indirect requires, `gorm.io/driver/sqlite` and `mattn/go-sqlite3`, exist because the test suite runs against SQLite. The `go 1.18` line is the floor for the library itself, which is why it keeps compiling on toolchains far older than the ones new Go releases ship.

## chainable_api.go and finisher_api.go are the contract

Two filenames in the root explain how to use GORM without reading the guides. `chainable_api.go` holds the methods that take a `*DB` and hand one back, the ones that add a condition and stay open. `finisher_api.go` holds the methods that run something, `First`, `Find`, `Create`, `Save`, `Delete` and their siblings.

That split is the whole mental model. Conditions accumulate on a chain and nothing reaches the database until you call something on the other side of the divide. It is why a builder can be passed around a function and completed by the caller, which is convenient, and why a chain that never reaches a finisher produces no error at all, which is not.

Two other root files extend the same idea rather than the feature list. `generics.go` sits beside `generics_withresult_test.go`, so a typed entry point and its result type are tested separately from the chainable surface. And `statement.go` holds the `Statement` type that a chain has been accumulating into, which is also what a plugin receives.

## Hooks are callbacks, which is why plugins can rewrite your SQL

The feature list names hooks for before and after create, save, update, delete and find. In the tree those are not a special case bolted onto the ORM, they are the same mechanism everything else extends: `callbacks.go` plus a `callbacks/` directory, with the chain of functions registered per operation.

One mechanism, several uses, and that is the point. A lifecycle hook is a callback that runs at a named point in an operation. A plugin that rewrites your queries is a callback registered at the same point. The feature list points at exactly that, naming the Database Resolver for multiple databases and read and write splitting, and Prometheus, as plugins rather than core features.

The consequence is worth stating plainly: with a plugin in the chain, the SQL that reaches your database is not the SQL you wrote. DryRun Mode exists for this, since it builds the statement without executing it, which is the only way to see what a plugin did to a query before it runs against real data. Read and write splitting in particular is invisible at the call site, because the same `Find` goes to a replica or a primary depending on what a callback decided.

## clause/ is a builder, so a subquery is a tree and not a string

The `clause/` directory is where SQL is assembled, and the feature list shows what it has to represent: the SQL builder, upsert, locking, optimizer and index and comment hints, named arguments, and the ability to search, update and create with a SQL expression.

Representing an index hint or a named argument as a string is where SQL builders usually go wrong, because quoting and parameter binding differ per dialect and per position. Building a clause tree instead means the dialect layer can render the same structure the way that database expects, which is the real reason GORM works across engines without you writing three versions of a query.

It also sets the boundary. The escape hatch for anything the builder cannot express is a raw SQL expression, and that path throws away exactly the guarantees the tree provides. It is the right tool for a database-specific function and the wrong tool for a filter, because there is no dialect rewriting and no parameter binding once you are writing the string yourself.

## schema/ derives your table names, and the pluralizer is pinned at v1.0.0

The `schema/` directory parses your structs into fields, tags and relationships, and the relationship types in the feature list, Has One, Has Many, Belongs To, Many To Many, Polymorphism and single-table inheritance, all resolve to a graph built there. Table naming happens in the same layer, and it is where `jinzhu/inflection` earns its place in the dependency list.

The consequence is that your table name is derived, not declared. Rename a struct and the table it maps to changes with it, and the migration story then depends on what the migrator in `migrator/` decides to do about a table that used to exist under the old name. `migrator.go` and `migrator/` implement the auto migrations the feature list advertises, so the naming decision and the schema change are the same decision.

One thing to check on day one rather than in production: the pluraliser dependency is pinned at `v1.0.0`, which is the first tagged release of that library. An irregular noun in your domain, a table you wanted named `people_data` or `policy`, is decided by that version's rules and not by current English usage.

## GORM Gen is the same project with a different answer

The README points at two guides, GORM Guides and Gen Guides, and the second one is the real alternative to consider. GORM Gen is the code generator published by the same maintainers, and it answers the same problem a different way.

The difference is when the type checking happens. GORM builds queries at runtime from a chain of method calls, so a column name that does not exist is discovered when the query runs. Gen generates Go source from your models ahead of time, so the queries are ordinary typed function calls in a file you check in, and a mismatch between the code and the schema is a compile error rather than a runtime error.

That trade is the whole decision. Runtime chains are quick to write and quick to change, and nothing regenerates when a model moves. Generated code is more work up front and then catches drift at build time, and a schema change turns into a regeneration plus a diff you can review. Teams that got burned by a production query against a renamed column tend to move toward Gen; teams iterating on a new schema stay on the chainable API.

## v1.31.2 landed eight months after v1.31.1

Three releases are on the feed: v1.31.0 on 2025-09-12, v1.31.1 on 2025-11-02, and v1.31.2 on 2026-06-25. The last push was on 2026-09-14 and the repository is not archived, so the branch is being worked on, but a patch release is not the unit of movement here.

Eight months between v1.31.1 and v1.31.2 is the number to plan around. If your team schedules dependency upgrades quarterly, expect GORM to be one of the entries that does not move, and expect a major version bump to be the event that requires attention. The feature list is stable enough to plan a long-lived 1.x against.

Licensing is MIT, with the repository stating it as released under the MIT License and a LICENSE file at the root. The copyright line reads `© Jinzhu, 2013~time.Now`, which is a Go idiom rather than a typo, and it also tells you the project predates the module path it now publishes under, since the module is `gorm.io/gorm` while the author and the earliest dependencies are still `jinzhu`.

## Conclusion

Adopt GORM if you want a productive Go data layer quickly, if you want Preload and Joins for eager loading rather than hand-written joins, and if you accept that read and write splitting arrives as a plugin rather than in the core. Do not adopt it if you want compile-time proof that a query matches your schema, because GORM builds SQL at runtime from a chain and a typo in a column name surfaces as a database error, not a build failure. Verify three things: which dialect module you added and whether its version tracks the core, whether the table names your structs produce match your existing schema, since naming runs through a pluralizer pinned at v1.0.0, and what the plugins in your chain do to a query, which you can inspect with DryRun before enabling them in production.

## FAQ

### What is golang GORM?

GORM is an ORM library for Go, described as aiming to be developer friendly, with hooks, eager loading through Preload and Joins, transactions with save points, prepared statement and dry run modes, batch insert, a SQL builder, auto migrations, composite primary keys and an extendable plugin API. The module path is `gorm.io/gorm` and the licence is MIT.

### What does GORM mean?

The repository describes itself as an ORM library for Golang, so the name is the ORM itself applied to Go. The core module has three direct dependencies and no database driver, which is why a dialect such as SQLite, MySQL or PostgreSQL is added as a separate module under `gorm.io/driver/`.

### What does the name GORM mean?

Beyond being an ORM for Golang, the repository does not explain where the acronym came from. What it does say is that the project is released under the MIT License with a copyright line reading `© Jinzhu, 2013~time.Now`, which dates the project well before the `gorm.io/gorm` module path it publishes under.

## Sources

- [go-gorm/gorm on GitHub](https://github.com/go-gorm/gorm)
- [License: MIT](https://github.com/go-gorm/gorm/blob/master/LICENSE)
- [Project website](https://gorm.io)
- [README](https://github.com/go-gorm/gorm/blob/master/README.md)
- [Releases](https://github.com/go-gorm/gorm/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/go-gorm-gorm
