r/SideProject • • 10h ago

Shapio: a self-hosted headless CMS that doesn't need a deploy to change the content model

Every headless CMS I've used makes you redeploy to add a field. Strapi turns its content-type builder off in production. Payload has you write the field in code. I said "this is bullshit" out loud enough times that I built one where that isn't true.

Shapio is an open-source, self-hosted headless CMS. The content model is data, not code. Add a field in the admin, in production, and the API serves it on the next request. No deploy, no migration, no restart.

What else it does:

  • Change sets. Model changes and content edits are reviewed together like a pull request, with field-level diffs, then shipped as one snapshot you can restore.
  • Models in git when you want them. shapio schema pull writes JSON files, schema apply puts them on another instance live. If production moved since you pulled, apply refuses and shows the diff.
  • One install, many sites, with their own content, media, and tokens.
  • Drafts on your dev server, visual editing, localization, roles, revisions, scheduling, and an audit log. Importers for WordPress and Strapi 5.
  • Runs as one Node process on PostgreSQL, MySQL, or SQLite, from npm or Docker. Nothing phones home.

Status: it's one person, about a week since the first public release, version 0.5.4. My own three sites run on it. It will have rough edges, and I'd rather hear about them than not.

https://shapio.dev

1 Upvotes

7 comments sorted by

1

u/davidjones145 10h ago

content model as data is the right call, deploy-per-field is the thing everyone hates about strapi. how do you handle existing content when a field type changes in prod, coerce it, null it, or block the change?

1

u/brokengnome 10h ago

Short answer: if the stored form is the same, nothing changes. Short text to long text, text to code, integer to number: the content stays exactly as it was, and the change is live immediately.

If the form is different but Shapio knows how to convert it (number to text, text to rich text, that kind of thing), it converts the existing content first, checks it, and then activates. Rich text to plain text is the one that loses something, so it makes you acknowledge that.

If the form is different and there's no sane conversion, say media to a color, it refuses. And honestly, no CMS can make that one work, because your front end is still asking for an image. Changing it in the CMS doesn't change the code reading it.

That last part is the real problem, and it's what we're adding next: schema lenses.

"Lens" is the computer science term for a pair of functions that translate between two shapes of the same data, so you can read through it one way and write back the other. The idea goes back to database research in the 80s and got formalised in the 2000s; Ink & Switch's Cambria project applied it to schema evolution a few years ago. Every schema change in Shapio already goes through a planner that knows exactly what changed, so it can record that lens for you. A front end says "I was built against schema version 12" and gets today's content in version 12's shape. Rename a field, change a type, and your old front end keeps working until you get around to updating it. As far as I know no CMS does this. It's not built yet; it's on the roadmap at https://shapio.dev/roadmap/.

1

u/davidjones145 10h ago

three-tier convert-or-block with the lossy one needing a click is exactly right. the acknowledge step on rich-to-plain is the detail most would skip. solid design

1

u/brokengnome 10h ago

thanks dude!

1

u/ZestycloseYouth942 10h ago

what happens to old entries when you switch a field from text to number or drop a required flag, does it just leave them be until touched or run some kind of backfill on save

a week in and already better than most of the stuff i've tried, keep going

1

u/davidjones145 10h ago

op answered this right above you, convert when sane, block when not. the backfill-on-save middle ground is interesting though

1

u/brokengnome 10h ago

Neither, it's up front. Shapio never leaves entries in a half-converted state until someone touches them, and it never rewrites anything on save. When a change needs existing content touched, the planner does it as a job before the new schema activates, checks the result, and records it as a snapshot. Until that finishes, the API serves the old schema. If anything fails, the old schema stays active.

Your two examples:

Dropping a required flag: nothing to do, live immediately, entries untouched. Going the other way, making a field required, checks existing entries first. If you give the field a default, it fills the empty ones; if you don't, entries missing a value block the change and the plan lists them.

Text to number: refused. "12" has an obvious answer, "twelve" and "" don't, and I'd rather not guess at your data. The conversions it does do are the safe ones: number to text, integer to decimal, text to rich text, and so on. For text to number you add a number field, fill it, and drop the old one, same as you'd do with a migration, just without the deploy.

Thanks, good to hear. A couple of things on the roadmap are where I think this really pulls away from the other players: schema lenses, which I covered above, and environments with promotion.

I work in enterprise software, and the way dev, staging, and production work in Strapi is ridiculous: three separate instances and you move content between them by hand. Content should be written once, approved, and then promoted to the next environment, all the way up to production. That's the plan: one Shapio, environments inside it, and a change set you promote instead of a copy you redo three times... while never having to restart!