Development

4 min read

This is bullshit. So I built a CMS.

Every headless CMS makes you deploy to add a field. I got sick of it and built Shapio, where the content model is data you change while the thing is running.

Pulp fight poster reading "Shapio vs everyone": the yellow Shapio S mark as a boxer in red gloves faces four sweating server-rack robots wearing belts labelled Strapi, Payload, Contentful and Sanity. Bottom line: add a field, no deploy.

Here is the moment every headless CMS has. You want to add a field. A subtitle, a second image, a flag that says this post is sponsored. Thirty seconds of work in any sane world. Instead you open Strapi's content-type builder and it tells you it is disabled in production. So you do it on your laptop, it writes a schema file, you commit the file, you run the pipeline, you wait, and if the migration disagrees with the data that is already there, you roll back and do it again. Payload skips the builder and has you write the field in TypeScript, then the same commit, the same pipeline, the same wait.

I have been running engineering teams for a long time, and I have watched a lot of smart people accept that as normal.

I said "this is bullshit" out loud more than once.

Then I did the thing I always do with a complaint, which is turn it into a project.

The complaint

The complaint, stated plainly: a CMS exists so that people can change content without a developer. But the shape of that content, the model, is treated as code. Code needs a build, a deploy, a restart. So the one thing a CMS should make easy is the one thing it makes slow.

Models as data

Shapio starts from the opposite premise. The content model is data. Every content type, every field, every component is a versioned row in the database. When you add a field in the admin, the server writes a new schema version, validates it, and starts serving it. The API routes are generic and look the model up on each request, so there is nothing to generate and nothing to restart. You can do it in production, on a Tuesday, with people reading the site.

The Shapio admin showing the Structure tab of a content type called Theme note, with its two fields, Title and Body, and an Add field button.
A content type in the builder. The status bar says it: schema live, no restart needed.

Git-shaped

The obvious objection is that this sounds like chaos. It is not, because the other half of the design is git-shaped. Every field and type has a stable ID that never changes, so renaming a label never touches stored data. You can pull the live model down to JSON files, review them, commit them, and apply them to another instance. If someone changed that instance while you were working, the apply is refused and shows you the diff, exactly the way a push is refused when the remote has moved. You get the safety of schema in a repository without the rule that the repository is the only place it can change.

Ship together

Content and schema ship together. A change set bundles the field you added and the entries that use it into one numbered snapshot. A site build pins a snapshot, so a half-finished change never leaks into a deploy, and you can rebuild any snapshot later and get exactly what was live then.

The boring parts

I also refused to sell the boring parts as add-ons. Localization is per field, with publishing per locale and fallbacks. Roles are per content type, field and action. Revisions, scheduled publishing and an audit log are in the open-source build, not in a tier. Several sites can share one instance, each with its own model, and a type can be shared across all of them when that makes sense. None of that is clever. It is just what a CMS should come with.

Self-hosted

It is self-hosted and does not phone home. You run it with npm or Docker, on PostgreSQL, MySQL or SQLite, and the only thing that leaves your server is what you point at it. There is a REST API and a GraphQL API with a playground, generated types from the live schema, and starters for Astro, Next.js and SvelteKit. If you want an AI assistant in the admin, you bring your own key, and it is off until you turn it on. There is an MCP server so your own tools can model and edit content.

The GraphQL page in the Shapio admin: the GraphiQL playground with its query editor and run button.
The GraphQL playground, in the admin, against the live schema.

I'm not just the creator, I'm also a user...

The part I care about most is that I use it. This site runs on it. So does the ForgeStack site. When I added the SEO group to the post type last night, it took one click in the builder, and the field was in the editor before I had switched tabs. That is the whole pitch, and it is the first time in years a CMS has done what I asked without making me wait.

The settings of an SEO field in the Shapio builder: label, API ID, help text, a Show in document switch turned on, and the Required, Public and Deprecated options.
The SEO field in the builder. Show in document puts it in the editor beside the body instead of in the settings panel.
The Shapio entry editor with the SEO group open beside a post: title with a character count, description, social image, canonical URL, a hide from search engines switch and a search result preview.
The SEO group in the entry editor, with a preview of the search result.

What it is not

What it is not: it is not ten years old, it does not have a plugin marketplace, and there is no hosted version yet. If you need those today, use something else and come back. If you are tired of deploying to add a field, the source is on GitHub under Apache-2.0, and the roadmap is a page on shapio.dev that I edit in the admin like everything else.

If you try it and something is bullshit, tell me. That is how this one started.