Development

2 min read

I got tired of restarting my CMS. So I built one.

Why Shapio treats content models as data, and what that changes for the people who use it.

Every headless CMS I have used in the last few years has the same moment. You add a field to a content model, and then you wait. The admin rebuilds, or the server restarts, or a migration runs and a deploy goes out. On a laptop it is a coffee break. In production it is a change request.

For a long time I assumed that was just the price of having types and a nice API. Then I spent a weekend helping a friend add a "subtitle" field to a blog, and it took two deploys and a rollback. That was the weekend I started sketching Shapio.

The core idea is small: content models are rows in a database, not files in a repository. When you add a field, Shapio writes a new version of the model, validates it, and starts serving it. The API routes are generic and look the model up at request time, so there is nothing to regenerate and nothing to restart.

That sounds risky until you add the other half, which is schema sync that works like git. You can pull the current models into JSON files, review them, commit them, and apply them to another environment. If someone changed production while you were working, the apply refuses and shows you the diff, the same way a push gets rejected when the remote has moved.

Content itself lives in JSONB, keyed by stable field IDs rather than names. Renaming a label never touches stored data. Changing an API ID is an explicit, flagged change, because that is a contract your front end depends on.

I also wanted the boring, important things built in instead of sold as add-ons: localization, revisions, scheduled publishing, roles, an audit log. Indie builders need those as much as anyone, they just cannot afford the enterprise tier.

Shapio is open source and self-hosted. You can run it as an npm package under PM2 or as a Docker image, against your own PostgreSQL. It does not phone home.

It is not finished. But this site is built on it, and the day I added a field to the Post model and saw it in the editor a second later, without touching a terminal, felt like the reason I started.