gophenberg: a plugin-first CMS with a Go backend and a Gutenberg admin
A plugin-first CMS. Go backend, React admin, Gutenberg editor.
At a glance
- What is it?
- Gophenberg is a content management system built as a Go service exposing a JSON API, with a React single page admin built on the WPDS and the Gutenberg block editor. Content types, plugins and themes are all registration based: content types come from the admin, plugins are Go packages compiled into the binary, and themes are Astro projects. The front end bundles WordPress packages, so the licence picture is more complicated than the Apache-2.0 badge suggests.
- Who is it for?
- gophenberg suits a team that wants WordPress style block editing, revisions and a media library, but on a Go backend with a JSON API and a React admin instead of PHP, and it suits anyone who wants to own their content stack rather than depend on a hosted CMS.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
WordPress editing, on a Go service with a JSON API
Gophenberg is described as a plugin-first CMS: a Go backend exposing a JSON API, with a React single page admin built on top of the WPDS and the Gutenberg block editor.
That combination is the whole idea. Gutenberg, the block editor, has become the default way people think about editing rich content on the web, and it is a block format rather than a page format, which means the same content model can be rendered by different front ends. WPDS gives the admin the familiar WordPress look without the WordPress codebase. The backend is Go, and everything the admin does goes through a JSON API rather than server rendered PHP.
The audience is therefore a team that wants the authoring experience of a modern CMS but does not want to run PHP, or wants a content layer they can query from services as well as from pages. The documentation lives at docs.gophenberg.org, and the repository carries a Discord badge for the community.
There is a second reading of the name, which the project does not make but which is hard to miss: Gutenberg and Berg, the mountain, with gopher for Go. Whether or not that was the intent, the project has a habit of naming things after their parts.
The licence deserves an early mention because it is not uniform. The original source is Apache-2.0 with SPDX headers, while the built front end bundles WordPress packages licensed under GPL-2.0-or-later, and those combined bundles are conveyed under the terms of the GPLv3. That is covered in detail further down.
What actually works, according to the README
The README has a section called What works today, and it is unusually concrete, which makes it the best available description of the project's scope.
Block content authored in the Gutenberg editor comes with autosave, revisions, trash and restore. There is a posts list with status filters, counts and sorting, plus a trash view. Content types are registered from the admin, each with its own addresses, an archive, and optional nesting. The media library stores uploads with derived image sizes, and it feeds the editor's media blocks both from a picker and from the inserter.
On the access side there is session authentication with login rate limiting, and a users screen for administrators. The routing is stated as a public site at the root, the admin under /admin, and a read-only content API at /api/content/v1.
That last path is the one to note. A read-only content API at a versioned path is what a static site, a mobile client or a design system consumes, and the fact that it is separate from the admin API means the public surface can be narrow.
The read-only content API is also what makes the theme model work, since a theme renders server side from that API, and the version segment means a theme can be written against a stable contract while the admin moves underneath it.
Themes are Astro projects, and the built-in renderer is the fallback
A theme is an Astro project that depends on @gophenberg/astro. You write your layouts, and the kit supplies the routes and the block rendering. The repository carries a working example in test/theme, and the documentation has a page on writing a theme that walks through it.
The division of labour is what makes this practical. A theme author is not implementing routing, not resolving content types, and not rendering blocks, because the kit does that. The theme supplies layouts and presentation.
The fallback behaviour is the part that deserves attention. Optional Astro themes work through @gophenberg/astro, and the built-in renderer takes over whenever no theme is active or a theme is down.
That is a good operational decision. A content site that renders nothing when its theme crashes is a broken site, and a built-in fallback means the content stays reachable. It also has a cost for anyone running a branded site: if the theme fails, the built-in renderer will quietly put up a different looking page rather than an error page. In a design controlled environment that fallback should be visible to the operators, because a visitor seeing the fallback is the only signal that the theme is broken.
The themes live in a directory the image hands to the non-root user, with the path set through the GOPHENBERG_THEMES_DIR environment variable, and the media directory works the same way with GOPHENBERG_MEDIA_DIR, so both are mounted into the container rather than baked into the image.
Plugins are Go packages compiled into the binary
The plugin model is compile time, and that is the single most consequential design decision in the project.
A plugin is a Go package under plugins/ with a plugin.json, and it is compiled in by make generate. The README says plugins own routes, public paths, and their own schema, and plugins/feed is the reference implementation, which serves the posts as an RSS channel.
The upside is the usual one for compiled plugins. A plugin is ordinary Go code in the same repository, with the same toolchain, the same type checker and the same test tooling, and it can reach into the internals of the CMS because it is part of the program rather than a guest in it. A plugin that owns routes and public paths can add an endpoint that the core knows nothing about, and the schema is declared in the plugin rather than in a central registry.
The cost is the deployment model. Adding a plugin is not a matter of dropping a file into a directory on a running server, it is a code change followed by a rebuild and a redeploy. For a team that ships content features through the same pipeline as code, that is often exactly what you want, because the plugin version and the content API version move together. For a team that wants site managers to enable features without a deploy, it is the wrong model, and the README does not describe a runtime plugin loader.
The tool directory in go.mod shows how the generation is driven. The module declares a tool block with the gremlins command, the sqlc command and apidiff, which is a coherent story for a project that generates code and then checks that its public API has not changed underneath its users.
Running it locally needs Go, Node 26, pnpm and a database
The local setup is documented in three commands and is worth following exactly, because the pieces fit together in a particular order.
The prerequisites are Go as pinned in go.mod, Node 26 with pnpm, and Docker for the PostgreSQL service. The first step copies the environment file and seeds the database:
cp .env.example .env
make seedThe README explains what the seed does. It starts PostgreSQL, applies the migrations, and creates a development administrator, [email protected] with the password password1234, alongside demo content.
Then the API and the admin run in two terminals:
make dev
pnpm install && pnpm --filter @gophenberg/frontend devThe API listens on localhost:8081 and the admin on localhost:5174, and the admin proxies /api to the API. So the development topology is the same as the production one in shape: one Go process on 8081, one admin application, a reverse proxy in front of the API path.
The database itself is a compose service, PostgreSQL 18 on a non-default host port 5435 mapped to 5432, with a named volume for the data directory and a pg_isready health check with a two second interval and fifteen retries. The migrations are handled by goose, which appears in the dependency list, and the query layer is generated by sqlc, which is why both tools are declared in the go.mod tool block.
The licence split is the first thing to read before you deploy
The README states the licence in two layers, and an adopter should read both.
Gophenberg is free and open-source software under the Apache License 2.0, and hand-written source files carry an SPDX-License-Identifier: Apache-2.0 header. The copyright line names Manuel 'SirLouen' Camargo.
Then the second paragraph. The built front end artifacts bundle @wordpress/* packages licensed under GPL-2.0-or-later, those combined bundles are conveyed under the terms of the GPLv3, and all original source files in the repository remain Apache-2.0.
So the repository you read is Apache-2.0, and the image you distribute carries a front end bundle whose licence obligations are different. That is the normal result of combining permissively licensed original work with GPL licensed dependencies into one distributed artifact, and it is exactly the kind of thing that a legal review should look at before you publish a container image. This is not advice either way, it is a description of what the project says about itself.
The repository reflects the same split in its files. There is a LICENSE file, a NOTICE file and a LICENSES/ directory, and the Dockerfile copies LICENSE, NOTICE and the LICENSES directory into the image at /usr/share/doc/gophenberg/, so the notices travel with the artifact rather than only existing in the source tree.
A Go service built from the Gopherium framework parts
The dependency list is unusual in an informative way, because many of the dependencies come from the same organisation as the project itself.
The module is github.com/gopherium/gophenberg, it requires Go 1.27.2, and among the direct requirements are github.com/gopherium/framework/gonsole with its auth package for both postgres and sqlite, github.com/gopherium/framework/pluginkit, github.com/gopherium/framework/dbkit with its own postgres and sqlite variants, and github.com/gopherium/framework/goncierge. The session authentication, the rate limiting and the plugin system all come from the gouncer and pluginkit packages rather than being written in the CMS itself.
Alongside those, the more conventional choices are there: chi for routing, pgx for the PostgreSQL driver, goose for migrations, godotenv for loading the .env file, bluemonday for HTML sanitising, godog for behaviour driven testing, and google/uuid. The gophenberg/sdk module appears as a dependency in its own right, which is how the theme kit and the generated client are shared.
The repository layout matches. There is cmd/ for the binary, internal/ for the server, frontend/ for the React admin, sdk/ with its own Go module and frontend and astro packages, plugins/ for the plugin packages, languages/ for the translation catalogues, test/ with end to end and theme projects, and docs/ for the documentation site. The tooling files show the same care: a .golangci.yml, an .oxlintrc.json, an eslint config, a knip configuration for unused exports, sqlc.yaml, and a codecov configuration.
Editorial conclusion
gophenberg suits a team that wants WordPress style block editing, revisions and a media library, but on a Go backend with a JSON API and a React admin instead of PHP, and it suits anyone who wants to own their content stack rather than depend on a hosted CMS. It does not suit a team that needs a mature plugin ecosystem today, because the plugin mechanism is young and the reference plugin is a single feed implementation, and it does not suit an organisation that cannot accept a distribution where the built front end is conveyed under the GPLv3. Check three things before you build on it: that the theme fallback behaviour suits you, since the built-in renderer takes over whenever a theme is absent or down, which is convenient but means your theme can silently stop being used, how the compile-time plugin model fits your roadmap, since a plugin is compiled into the binary rather than loaded at runtime, and the licence terms for the image you intend to distribute. The last push to the repository was on 2026-10-22 and the newest release is v0.19.0 from 2026-10-15.
Frequently asked questions
What is Gophenberg?
It is a plugin-first CMS with a Go backend exposing a JSON API and a React single page admin built on the WordPress design system and the Gutenberg block editor. Content is authored as blocks, and the admin, the public site and the content API are separate surfaces.
How do I run Gophenberg locally?
Copy .env.example to .env and run make seed, which starts PostgreSQL, applies the migrations and creates a development administrator at [email protected] with the password password1234 plus demo content. Then run make dev for the API and pnpm install and pnpm --filter @gophenberg/frontend dev for the admin, with the API on localhost:8081 and the admin on localhost:5174.
How are plugins written for Gophenberg?
A plugin is a Go package under plugins/ with a plugin.json, compiled in by make generate. Plugins own routes, public paths and their own schema, and plugins/feed is the reference implementation, serving the posts as an RSS channel.
How do themes work in Gophenberg?
A theme is an Astro project depending on @gophenberg/astro, where you write the layouts and the kit supplies the routes and the block rendering, with a working example in test/theme. The built-in renderer takes over whenever no theme is active or a theme is down.
What licence does Gophenberg use?
The original source files are under the Apache License 2.0 with SPDX headers, but the built front end artifacts bundle @wordpress/* packages under GPL-2.0-or-later, and those combined bundles are conveyed under the terms of the GPLv3. The image copies LICENSE, NOTICE and the LICENSES directory so the notices travel with it.
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/gopherium-gophenberg)Was this useful?