Dan Matthew
Service

Operator test

System
Nimbus CMS
Skin
Attract mode (arcade)
Entries
on this screen
Menus
8 items
Path
/blog/nimbuscms-let-a-real-app-pull-features-into-core
Credits
Free play
Press ~ to toggle · Esc to close

Writing · 6 Sep 2026

I let a 2014 restaurant app decide what belongs in my CMS core

The hardest question in building a CMS is not which feature to add next, it is whether a feature belongs in the core at all. I stopped guessing and let a real application answer.

#nimbuscms #php #architecture #plugins

The hardest question in building a CMS is not which feature to add next. It is whether a feature belongs in the core at all. A core is the part you cannot easily take back later, so every wrong guess is permanent. I kept guessing, so I stopped guessing and let a real application decide for me.

NimbusCMS is a small PHP CMS with a deliberately tiny core, where everything application specific lives outside it: in plugins, in themes, in the application's own code. To keep the core honest I made a rule. I do not add a capability because I imagine someone might want it. I add it only when a real app running on the platform proves it needs it, in a way many unrelated apps would.

The probe was my oldest repo

To test that, I rebuilt my very first project, a 2014 restaurant management system (procedural PHP, MD5 passwords, would not even start on a modern runtime), as an application on Nimbus. Not to bring the restaurant back. To use it as a probe.

The bet was simple. If a whole restaurant, the floor and orders and kitchen and payments and staff roles and reservations and reports, could be built without pushing a single line of restaurant logic into the CMS core, then the platform was real. And if it could not, the gaps would tell me precisely what the core was missing.

The rule I held the entire time: zero restaurant logic in core. The restaurant is a co-located plugin, a theme, and a few content collections. The words "menu" and "table" appear nowhere inside Nimbus itself.

What the app actually pulled into core

Building it surfaced a short list of honest gaps. Each one became a small, general capability with its own decision record, useful to any app, not just a restaurant.

  • The orders screen needed to read the menu, which lives in a content collection. A Nimbus plugin deliberately cannot read content (that boundary is worth its own post). So I added a read only, published only content facade. A restaurant reads its menu through it, a shop reads its catalog, a docs site reads its pages.
  • Staff roles needed a finer gate than read and write: a cook runs the kitchen but must never take payment. So a plugin's capability went from a fixed read or write pair to any named action the plugin declares. A floor, kitchen, and manage split is just one app's use of a general mechanism.
  • The project cards on this very site needed to embed my own apps in an iframe, which the strict framing policy forbids by default. So a per site embedding allowlist, opt in per origin.
  • And a plain bug the app found: the admin dashboard counted every collection and entry while ignoring the per collection permissions the rest of the admin enforces, so a role that could not open a collection still saw its count. That got fixed in core, because it was a core inconsistency the restaurant merely happened to reveal.
A real app hits a wall Name the gap, not the app Write a decision record Small core capability reused by any app restaurant, shop, docs
⤢ Enlarge

The one question that keeps this safe

The danger of letting a single app drive a platform is obvious: you build a restaurant shaped CMS by accident. The guard is one question I ask before anything is allowed into core. Would I still add this if the restaurant did not exist?

If the honest answer is no, it is a plugin, not core. The read only content facade passes, because every plugin that composes with content wants it. A "table status" value would fail, because only a restaurant wants it, so it lives in the plugin, on the plugin's own tables, touching no core data.

What this does not prove

This is validation by one app, and one app can mislead. A single consumer is evidence, not proof. I only treat a capability as wanted by many when I can name the unrelated sites that would use it, not when I can merely imagine them. Nimbus is a 0.1.0 beta, and I am deliberately slow to freeze the public surface, because a core capability, once public, is hard to walk back.

The restaurant is live now, and every vertical of the old system runs on Nimbus with none of it in the core. The useful output was never the restaurant. It was the four small general things the restaurant forced me to build, and the dozens of restaurant specific things it did not.