# casbin-gorm-adapter: storing Casbin policy in a SQL database

> The GORM adapter lets a Casbin enforcer load and save policy from any GORM-supported database, with MySQL, PostgreSQL, SQL Server and SQLite listed, a custom column layout option and a transactional adapter built on a two-phase commit protocol. It is a storage adapter and nothing more, and the policy model stays in Casbin.

**apache/casbin-gorm-adapter** — GORM adapter for Casbin, see extended version of GORM Adapter Ex at: https://github.com/casbin/gorm-adapter-ex

- Repository: https://github.com/apache/casbin-gorm-adapter
- Website: https://github.com/casbin/casbin
- Stars: 752 · Forks: 220
- Language: Go
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/apache-casbin-gorm-adapter

## What the adapter is and what it is not

Gorm Adapter is the GORM adapter for Casbin. With this library, Casbin can load policy from a GORM-supported database or save policy to it.

That is the whole scope, and the topic list confirms it: adapter, storage-driver, authorization, authz, rbac, abac, acl, go and golang. The adapter is the storage layer between an enforcer and a SQL table. The model, the matching logic and the enforcement decision all stay in Casbin itself, which is the important division.

The supported databases are listed as MySQL, PostgreSQL, SQL Server and Sqlite3, following the officially supported databases in GORM, plus other third-party supported databases listed on the GORM website or elsewhere.

One implementation choice is worth explaining. The adapter uses github.com/glebarez/sqlite instead of GORM's official sqlite driver gorm.io/driver/sqlite, because the latter needs cgo support. The README notes there is almost no difference between the two drivers, and asks for an issue if a difference shows up in use. That single swap means the adapter can be used in a pure Go build with no C toolchain, which is the difference between a library you can use in a scratch container and one you cannot.

The repository description also points to an extended version at github.com/casbin/gorm-adapter-ex, which is a separate project for the cases this one does not cover. The main repository is deliberately the smaller one, with the adapter.go file at the root and the example models and policies in an examples/ directory.

## Installing and wiring the adapter into an enforcer

The module is fetched with the usual Go tooling.

```bash
go get github.com/casbin/gorm-adapter/v3
```

The v3 suffix in the import path is the module major version, and the go.mod requires Go 1.24.0 with Casbin at v3.8.1 and GORM at v1.31.1.

The MySQL example in the README is the shortest complete path from an empty database to an enforcing enforcer.

```go
gormadapter "github.com/casbin/gorm-adapter/v3"
_ "github.com/go-sql-driver/mysql"

a, _ := gormadapter.NewAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/") // Your driver and data source.
e, _ := casbin.NewEnforcer("examples/rbac_model.conf", a)
```

The comments in the example carry the operational details. The adapter will use the MySQL database named casbin, and if it does not exist the adapter creates it automatically. You can also use an already existing GORM instance with gormadapter.NewAdapterByDB(gormInstance), which is the route to take when your application already has a database handle.

The same shape works for PostgreSQL with the lib/pq driver imported for its side effect.

```go
a, _ := gormadapter.NewAdapter("postgres", "host=127.0.0.1 port=5432 user=postgres password=postgres sslmode=disable") // Your driver and data source.
```

After the enforcer exists, the working cycle is load, check, modify, save.

```go
e.LoadPolicy()
e.Enforce("alice", "data1", "read")
e.SavePolicy()
```

The commented-out lines in the example show the modification calls, e.AddPolicy and e.RemovePolicy, and the table name the adapter uses by default, casbin_rule, which it also creates if it does not exist.

## AutoMigrate, custom columns and the table name change

Three configuration points decide how the policy table is created, and each of them has a migration story.

The first is AutoMigrate. Creating an adapter uses AutoMigrate by default to create the table, and to turn it off the README gives the API TurnOffAutoMigrate(db *gorm.DB) *gorm.DB. The example shows it being called on a freshly opened GORM handle before the adapter is constructed, and the constructor variant NewAdapterByDBWithCustomTable is used in the commented code.

The second is the column layout. You can change the GORM struct tags, but the table structure must stay the same. The example defines its own CasbinRule type with a primary key and six version columns.

```go
type CasbinRule struct {
	ID    uint   `gorm:"primaryKey;autoIncrement"`
	Ptype string `gorm:"size:512;uniqueIndex:unique_index"`
	V5    string `gorm:"size:512;uniqueIndex:unique_index"`
}
```

The example increases the column size to 512 and keeps the unique index on every column, which is what makes a rule set unique. Passing that struct to NewAdapterByDBWithCustomTable with an existing database handle is the whole mechanism.

The third is the table name, and it is the one that bites on upgrade. The README opens with a note: in v3.0.3 the method NewAdapterByDB creates a table named casbin_rules, and it was fixed to casbin_rule after that. If you used v3.0.3 or less and want to update, you might need to migrate the data manually, with a link to the issue for the details. A schema change in a patch release, of all the things that are awkward to roll back, and the fix landed in a version that shares its major number with the broken one.

## The transactional adapter and its two-phase commit

The newest capability in the README is the transactional adapter, and the wording is specific about how it works.

You can modify policies within a transaction, and the new implementation uses a two-phase commit protocol and supports multi-threaded environments. The construction mirrors the other adapters, with a transactional adapter built from an existing database handle and a transactional enforcer instead of the ordinary one.

```go
adapter, _ := gormadapter.NewTransactionalAdapterByDB(db)
e, _ := casbin.NewTransactionalEnforcer("examples/rbac_model.conf", adapter)
```

The transaction itself is a method on the enforcer that takes a context and a function.

```go
ctx := context.Background()
err := e.WithTransaction(ctx, func(tx *casbin.Transaction) error {
	tx.AddPolicy("alice",
```

WithTransaction executes a function within a transaction. If the function returns an error the transaction is rolled back, and otherwise it is committed automatically.

The two-phase commit is the significant detail, because a plain database transaction cannot cover a policy change that also has to be reflected in the enforcer's in-memory state. A two-phase protocol lets the adapter prepare the change, then commit it, and the multi-threaded support matters because the enforcer is shared across goroutines in a typical service.

This is the piece that a plain adapter cannot do, and it is the reason to look at the transactional variant rather than calling SavePolicy after a batch of changes.

## Where the GORM adapter is the wrong choice

The first limitation is the table shape. The table structure must stay the same, which means the rule columns are the fixed V0 to V5 plus the policy type, and the only real freedom is the column size and the tags. A policy model that needs a different shape, more fields, a different index strategy or a normalised representation is outside this adapter, which is what the extended variant in the separate gorm-adapter-ex repository is for.

The second is the scale model. The adapter loads and saves a whole policy set, and the transaction support is for changing policies, not for querying them per request. Casbin's own enforcement is in memory, so the database is not in the request path once the policy is loaded, and a deployment with a very large policy set pays for loading it. An adapter that stored individual rules in a form you could query with SQL would be a different design, and this is not it.

The third is the dependency weight. GORM brings in a driver per database, the go.mod lists drivers for MySQL, PostgreSQL and SQL Server plus the pure Go SQLite, along with dbresolver for read replica support, and Casbin itself brings govaluate. That is a substantial dependency tree for what is, functionally, a policy table.

The fourth is the version fragility already mentioned, with the table name changing in v3.0.3 inside the same major version, and the README's advice being that you might need to migrate the data manually. Any adapter that owns a table has that risk, but a policy table in particular holds data you cannot regenerate, since it is the source of truth for who can do what.

The fifth is the SQLite caveat in practice. The pure Go driver is convenient, but SQLite's concurrency characteristics are not the same as a server database, and the adapter's own documentation points at PostgreSQL or MySQL examples. A production deployment that ends up on SQLite because the driver was convenient has made a decision by accident.

## Licence, maintenance and the cost of policy in the database

The project is under the Apache-2.0 licence, with the LICENSE file at the root of the repository, and the homepage points at the Casbin project itself rather than a separate site.

The repository is not archived and the last push was on 2026-05-15, so the code is being maintained. The release history shows v3.41.0 in January 2026, v3.40.0 earlier that month and v3.39.0 in December 2025, so the project ships patch releases steadily. There is a Go Report Card badge, a CI workflow badge, a Coveralls coverage badge, a godoc badge and a Sourcegraph badge, which is a well-instrumented repository by open source standards.

The cost of keeping policy in the same database as the application is the main design question. The benefit is transactional consistency: a policy change and the business data it governs can be committed together, and with the two-phase adapter, the enforcer's in-memory state can be brought into line rather than left stale. The cost is that the database now holds two very different kinds of data, a business schema that changes with the product and a policy schema that a Casbin upgrade might want to change, and a schema migration plan that has to account for both.

The practical mitigation the adapter offers is the custom table type, which lets you keep the columns but adjust the sizes, and the AutoMigrate switch, which lets you take the table creation out of the hands of the library when your migrations should own it. Both are worth using deliberately rather than by default, since the default is convenient for a first run and wrong for a production database where the schema is managed by a migration tool.

## Conclusion

casbin-gorm-adapter suits a Go service that already runs GORM and wants Casbin policy in the same database, since it removes the need for a separate policy store and gives you transactions, custom column sizes and a read replica through dbresolver. It does not suit a project that needs more than the standard rule table, since the extended variant with more features lives in a separate repository. Verify the Casbin major version you are on before upgrading, because the v3.0.3 change to the table name needs a manual data migration.

## FAQ

### How do I install the Casbin GORM adapter?

Fetch the module with go get github.com/casbin/gorm-adapter/v3, which requires Go 1.24.0, Casbin v3.8.1 and GORM v1.31.1. Then create the adapter with NewAdapter for a driver and data source, or NewAdapterByDB if you already have a GORM instance, and pass it to casbin.NewEnforcer.

### Which databases does the Casbin GORM adapter support?

MySQL, PostgreSQL, SQL Server and Sqlite3, following the officially supported databases in GORM, plus other third-party supported databases. It uses the pure Go github.com/glebarez/sqlite driver rather than GORM's official SQLite driver because the latter needs cgo support.

### Why did my Casbin policy table change name after upgrading the adapter?

In v3.0.3 the method NewAdapterByDB created a table named casbin_rules, and it was fixed to casbin_rule after that. The README notes that if you used v3.0.3 or less and want to update, you might need to migrate the data manually.

### How do I change the policy table columns with the Casbin GORM adapter?

Define your own struct with the GORM tags you want and pass it to NewAdapterByDBWithCustomTable, but the table structure must stay the same. The README's example raises the column size to 512 while keeping the unique index on each column, and NewAdapterByDBWithCustomTable takes an existing database handle.

## Sources

- [apache/casbin-gorm-adapter on GitHub](https://github.com/apache/casbin-gorm-adapter)
- [License: Apache-2.0](https://github.com/apache/casbin-gorm-adapter/blob/master/LICENSE)
- [Project website](https://github.com/casbin/casbin)
- [README](https://github.com/apache/casbin-gorm-adapter/blob/master/README.md)
- [Releases](https://github.com/apache/casbin-gorm-adapter/releases)

---

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