← writing

Squilla in one paragraph (and then a few more)

A short introduction to Squilla, my own CMS. One paragraph elevator pitch, then a few more on why it is shaped the way it is.

People keep asking me what Squilla actually is, and I keep giving rambling answers. So here it is in one paragraph, and then a few more for anyone who wants the why behind the what.

Squilla is a CMS shaped like a Linux box. A small Go kernel handles auth, content nodes, and a CoreAPI. Everything else (media, email, SEO, forms, sitemaps) is an extension you can swap or remove. The admin UI is a shell that loads each extension's micro-frontend at runtime. It's AI-native: the CoreAPI is also exposed over MCP, so Claude can run the site.

Why kernel plus extensions

The thing that broke my patience with monolithic CMSes is that you can never really turn anything off. You disable a feature in settings and the code is still sitting in the binary, still owning routes, still firing hooks, still costing you cognitive load every time you read the codebase. I wanted a CMS where removing a feature meant the code was actually gone.

So I copied the shape of a Linux distribution. The kernel is tiny and boring. It does the things every CMS must do, like content nodes, authentication, rendering, and exposing one clean API for everything else to talk to. Features are packages. Drop them in, activate them, and they bring their own database tables, their own HTTP routes, their own admin pages, and their own React micro-frontend. Remove them and the kernel does not notice they were ever there.

There is one hard rule I keep coming back to. If removing an extension would leave dead code in the kernel, the code was in the wrong place to begin with. That rule is annoying because it pushes work back into extensions even when it would be quicker to just dump something into core. But it is the rule that keeps the kernel honest. Image optimisation, WebP conversion, email template management, SEO defaults, sitemap generation, none of them live in the kernel. They live in the extension that owns them. The kernel just provides the bus.

Why Go

Go is boring, fast, and ships as a single binary. That is the whole pitch. I do not have to think about runtime managers, virtual environments, or whether the production server has the right version of anything. The build output is one file. I copy it to a server, run it, and the CMS is up. Cold start is fast enough that I do not need a warm-up phase, and TTFB on public pages sits comfortably under fifty milliseconds without any cache tricks.

There is a second reason. Go is good at the thing CMSes actually need, which is glue. Concurrent request handling, structured logging, gRPC for plugin isolation, atomic operations for hot-swapping config. None of these are exciting in isolation, but doing all of them in one language without fighting the runtime makes the rest of the system feel light.

Why JSONB content nodes

Every page, post, recipe, case study, and weirdly shaped content type in Squilla is the same database row. It has a slug, a title, a status, some metadata, and a JSONB column called blocks_data that holds the actual content. New content types do not need migrations. They are just a schema declaration that says here is a node type called recipe, here are its fields, here are the blocks it supports.

This sounds risky if you come from a strict relational background, but JSONB in Postgres is fast, indexable with GIN, and queryable with real SQL. The kernel gets one shape to render, one shape to render previews of, one shape to revision and restore. Extensions can still own their own proper tables when they need them, but the content layer itself stays uniform.

Why Tengo for scripts

Themes and small extensions can ship Tengo scripts for event hooks, route handlers, and filters. Tengo is a sandboxed scripting language that runs inside the Go process, so there is no node_modules to install, no separate runtime to keep alive, and no way for a theme script to break out and do something nasty to the host. It exposes the CoreAPI through core slash star modules, so a theme script that wants to fetch nodes calls core.nodes.query and gets the same data the admin UI would.

The point is not that Tengo is the best language ever written. The point is that for the kind of small glue scripts a theme needs, it is sandboxed by default, embedded by default, and zero dependency. A theme is one folder. You drop it in, activate it, done.

The honest framing

This is not a WordPress killer. It is not trying to be. It is a different bet for builders who like small kernels, who get nervous when they cannot see the edges of a system, and who want their CMS to feel like a tool they understand all the way down. If that sounds like you, Squilla might be interesting. If you want a giant ecosystem of plugins written by strangers, you already know where to go.

I will write more about the individual pieces, the extension system, the MCP layer, the theme engine, in posts to come. For now, that one paragraph at the top is the whole shape. The rest is detail.

Want more like this?

Occasional, opinionated, no listicles.
all writing →