qor/admin: a GORM backed admin panel and JSON API in Go
Qor Admin - Instantly create a beautiful, cross platform, configurable Admin Interface and API for managing your data in minutes.
At a glance
- What is it?
- qor/admin generates an admin interface and a RESTful JSON API over your existing GORM models, with resources, search, filters, actions, association handling and a permission model. It is a Go library in a repository whose JavaScript assets are built separately, and it still carries go path era template lookup quirks.
- Who is it for?
- Adopt qor/admin when your Go service already uses GORM models and you need an internal admin surface plus a JSON API over the same records, since the whole setup reduces to registering resources on a mounted admin instance.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
An admin panel generated from the models you already have
qor/admin is a Go library that takes your GORM models and turns them into a web admin interface and a RESTful JSON API. There is no separate admin application to run and no migration of your data into a CMS store. The unit of work is a resource, and a resource is derived from a struct.
The feature list in the project description names what you get: an admin interface for managing data, a RESTful JSON API, association handling, search and filtering, actions and batch actions, authentication and authorization, and extendability. That is a fairly complete brief for an internal back office, and the notable thing is how little of it is bespoke.
The code base layout shows how the pieces divide. resource.go holds the resource abstraction, controller.go and route.go and route_handler.go deal with HTTP, searcher.go and filter.go with querying, action.go with the action and batch action machinery, auth.go with authentication and authorization, meta.go with the metadata system, and there are per meta type files such as meta_select_one.go, meta_select_many.go, meta_datetime.go and meta_rich_editor.go. Section and menu files, a theme file and a settings file sit alongside them, and transformer.go with transformer_json.go and transformer_xml.go handle response shaping.
The repository is primarily JavaScript by language detection because of the front end assets, but everything in the README and in the Go files is Go, and the module path is github.com/qor/admin.
The shortest possible admin, in about forty lines
The quick start is a complete program. It defines two GORM models, opens a SQLite database, migrates them, creates an admin instance with that database, registers both models as resources, mounts the admin to a path on an HTTP mux and serves.
package main
import (
"fmt"
"net/http"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
"github.com/qor/admin"
)The main function then wires the pieces together.
func main() {
DB, _ := gorm.Open("sqlite3", "demo.db")
DB.AutoMigrate(&User{}, &Product{})
// Initialize
Admin := admin.New(&admin.AdminConfig{DB: DB})
// Allow to use Admin to manage User, Product
Admin.AddResource(&User{})
Admin.AddResource(&Product{})
}The interesting line is Admin.AddResource. Passing a pointer to your model is enough to get a list view, an edit form, a create form and a delete action, because the resource reads the struct fields and the GORM metadata. You did not write any view, any route or any template.
Once it runs, the README tells you to run it with go run main.go and visit localhost:9000/admin to see the result. The mux mounting is what puts the admin under a path rather than at the root of your site.
mux := http.NewServeMux()
// Mount admin interface to mux
Admin.MountTo("/admin", mux)
fmt.Println("Listening on: 9000")
http.ListenAndServe(":9000", mux)That is the whole contract. Everything else in the project is configuration layered on top of these five calls.
Metadata is where the behaviour of a resource actually changes
Once a resource exists, the interesting work happens through metadata objects. A Meta has a name, a value, a configuration struct, and a Valuer function that can transform how the field is rendered and how it is read back.
The clearest example in the documentation is association selection. When a model has many items and you want the editor to offer a remote picker rather than a plain list, you define a separate selector resource and then point the association field at it through a meta with a SelectManyConfig.
factoryRes.Meta(&admin.Meta{
Name: "Items",
Config: &admin.SelectManyConfig{
RemoteDataResource: itemSelector,
},
})For a has one relationship the same shape appears with SelectOneConfig and a RemoteDataResource, which is the pattern used for a Manager field on a Factory model.
The metadata system also lets you override how a collection is produced. The documentation shows a Collection function on a SelectManyConfig that queries related items itself and builds the result rows by hand, with the first element of each row being a composite primary key rather than a plain ID.
This is where the library stops being a black box. Meta values can be given per field, and configuration types exist per widget: select one, select many, datetime, rich editor, collection edit and single edit all live in their own files, which tells you the surface a real project touches.
Composite primary keys, versioned models and the publish2 dance
A section of the documentation deals with a specific and common pain: models that carry versioning, where the publish2.Version embedded struct adds a version name to the record. When such a model is on the many side of a relationship, the identifier used by the selector is no longer a single ID.
The fix has two parts, and both are marked required in the code comments. The model on the many or one side must embed resource.CompositePrimaryKeyField from the github.com/qor/qor/resource package. Then the selector resource has to define an ID meta whose Valuer converts the single ID into a composite primary key.
res = adm.AddResource(&Item{}, &admin.Config{Name: "ItemSelector"})
res.IndexAttrs("ID", "Name")The Valuer calls resource.GenCompositePrimaryKey with the record's ID and its version name, so what the selector returns is the pair the association field expects. The documentation states that configuring the ID meta this way is mandatory for composite primary key support.
There is a third piece for the versioned workflow. To assign associations when creating a new version of an object immediately, you define a function called AssignVersionName on the versioned struct with a pointer receiver, containing the logic for generating the new version name and assigning it to the object. The sample implementation counts existing versions for the same day and formats the name as a date plus a version counter.
Where the front end assets and the template lookup come from
Two details catch teams out, and both are documented, but neither is obvious from the outside.
The first is the asset build. The repository contains package.json with Babel and gulp-babel in the dev dependencies, and its main entry is the compiled file admin/views/assets/javascripts/app.js. The Go package.json file describes itself as an enterprise level open source content management system, uses yarn as its package manager, and there is a yarn.lock in the tree. So the JavaScript front end is compiled separately and committed, which is why the repository's primary language reads as JavaScript even though the library itself is Go.
The second is view path resolution, and the project explains it as a historical artefact: QOR was developed before go mod was introduced, so it still supports go path while finding its template files. The priority order is to check vendor first, then check the module cache path at $GOPATH/pkg/mod/github.com/qor/[email protected]/views, where the version is detected automatically from your go.mod file, and finally load from $GOPATH/src/github.com/qor/admin/views.
There is a practical trap here. If you want the templates from the module cache to be used, you have to make sure the go path source copy is absent, otherwise the first one found wins and you may be running old templates against a new library version.
Dependencies, versions and the gap between releases and commits
The module declares go 1.26.0, so your project has to be on a compatible toolchain line. The direct dependencies tell you what this library is built on and, importantly, which generation of GORM it expects: github.com/jinzhu/gorm v1.9.16, the original GORM, not the v2 rewrite under the gorm.io path. The rest of the direct requirements are the qor family, including qor v1.3.1, media, assetfs, responder, roles and session, plus fatih/color, jinzhu/inflection, jinzhu/now, and theplant/cldr for locale data.
The transitive list is long and includes AWS SDK v2, lib/pq, the MySQL driver, gorilla/sessions and securecookie, bluemonday for HTML sanitising, and the plant testing utilities.
On releases, there is a gap worth knowing about. The listed tags are v1.2.0 from 2021-06-30, v1.2-beta.1 with group permission support from 2020-08-12, and v1.1 from 2019-04-23. Yet the repository is not archived and the last push was on 2026-10-19, and several dependencies are pinned to pseudo versions dated in 2026. In other words the project is still being worked on while the release tags stay where they were, so if you pin a version you may be missing recent fixes, and if you track the branch you are moving with commits that have no tag.
The licence is stated in the README as MIT.
When this is the wrong tool for your admin
Three cases stand out.
First, if your data layer is not GORM v1, there is no path in. The resource system is built on GORM models and the gorm.Model embed, so an ORM agnostic admin would need a different project.
Second, if you need a modern front end build pipeline, this is friction. The templates and the JavaScript assets are served from the library in a form that predates go modules, and the view path resolution has a documented trap where a stale go path copy shadows the module cache. Teams that care about their own asset pipeline and theming will spend time fighting that rather than enjoying it.
Third, if you need multi tenancy, row level security or an audit trail out of the box. The features list covers authentication and authorization, and the auth.go file and the roles dependency show that permissions are modelled, but the documentation here does not describe tenant isolation, and the scope of the authorization model is not spelled out on this page.
One last practical note about the security process. The project asks that security issues be sent by email to [email protected] with time to respond before posting as an issue or reporting on public forums. If you are mounting an admin interface on a public host, that disclosure route matters.
Editorial conclusion
Adopt qor/admin when your Go service already uses GORM models and you need an internal admin surface plus a JSON API over the same records, since the whole setup reduces to registering resources on a mounted admin instance. Do not adopt it if your models are not GORM backed, if you need a modern template asset pipeline, or if you are starting a fresh project, because the library still resolves view files through go path locations and its JavaScript assets are compiled separately. Verify first that the go.mod line for your project satisfies the go 1.26.0 directive this module declares, that your GORM version matches the github.com/jinzhu/gorm v1.9.16 dependency rather than the v2 line, and that the MIT terms in the README match the licence expectation of your organisation before you ship it.
Frequently asked questions
How do I create an admin panel in qor/admin?
Open a GORM database, create an admin instance with admin.New(&admin.AdminConfig{DB: DB}), register each model with Admin.AddResource(&User{}), and mount it with Admin.MountTo("/admin", mux) on an HTTP mux. The quick start then runs with go run main.go and the panel appears at localhost:9000/admin.
How do I add a remote selector for a has many relationship in qor/admin?
Add resource.CompositePrimaryKeyField to the model on the many side, create a selector resource with admin.AddResource and configure an ID meta whose Valuer returns resource.GenCompositePrimaryKey with the ID and version name, then attach that resource to the association field through a meta with a SelectManyConfig and RemoteDataResource.
Which GORM version does qor/admin require?
The go.mod file requires github.com/jinzhu/gorm v1.9.16, the original GORM, along with a go 1.26.0 directive. The library also depends on the qor family of packages including qor v1.3.1, media, assetfs, responder, roles and session.
How do I print the routes registered by qor/admin?
The documentation shows a single call for that, adm.GetRouter().PrintRoutes(), where adm is a QOR admin instance. The same router object is what Admin.MountTo attaches to your HTTP mux.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/qor-admin)Was this useful?