Hello World from Theme Script!

A CMS that an AI can actually run.

Squilla is a Go kernel with everything else as a hot-loaded extension. Every operation — content, themes, blocks, taxonomies, settings, media — is exposed as an MCP tool. No filesystem hacks. No HTML scraping. Just a structured API and a render preview.

Read the standards

The shape of the platform

~75
MCP tools shipped
17
Domains under core.*
0
Restarts to drop in a theme or extension
<50ms
Public TTFB target

Six surfaces. One contract.

Squilla looks small from the outside because everything that isn't infrastructure ships as an extension. The kernel only owns nodes, auth, rendering, the event bus, and the CoreAPI — every other capability is a Debian-style package with its own tables, migrations, and admin UI.

🧩

Kernel + extensions

If removing a feature leaves dead code in core, that code belonged in an extension. Media, email, forms, sitemaps — all opt-in, capability-guarded packages.

🤖

MCP-first

Every CMS operation is an MCP tool. core.guide returns a goal→tool decision tree; core.theme.standards and core.extension.standards are the authoring contract.

📦

Hot drop-in

Drop a theme or extension folder into the running container — an fs watcher debounces, rescans, and registers it. No restart. No rebuild.

🔌

gRPC plugins

Extensions ship as Go binaries spawned via HashiCorp go-plugin. Crashes are isolated. Activation hot-spawns the subprocess in milliseconds.

📜

Tengo scripting

Sandboxed embedded VM with core/* modules — nodes, taxonomies, terms, menus, settings, events, http, assets. Themes self-bootstrap from a single .tengo file.

🛡️

Capability-guarded

Every CoreAPI call goes through a capability guard reading the caller's manifest. Declare data:read, you can read; you don't get data:write for free.

Scaffold a working site in four MCP calls.

No filesystem walks. No SQL. No restarts. Each step is one tool call, idempotent, and verifiable via core.render.node_preview.

Activate a theme

core.theme.list to find the ID, core.theme.activate to swap the asset registry atomically. The site serves the new look on the next request.

Define a content type

core.nodetype.create with a field_schema. Pages, posts, recipes, trips — whatever fits. Custom field types contributed by extensions show up automatically.

Upload media + create the node

core.media.upload returns a slug-addressable record; core.node.create accepts blocks_data and a featured_image object. AI agents reference media by slug for theme portability.

Verify the render

core.render.node_preview returns the full layout + blocks + theme CSS as the public site would serve it. Side-effect-free, safe to call repeatedly.

The kernel is the boring part. That's the point.

Squilla's core ships exactly what every CMS needs and nothing more: content nodes, auth, rendering, an event bus, and a capability-guarded CoreAPI. Everything that could be opinionated — media management, email delivery, SEO, forms, image optimization — lives outside, in extensions.

  • Disable an extension and core stays clean
  • Replace email providers without touching templates
  • Crashes in one plugin can't take down the others
Read the architecture doc

An admin shell that's just… a shell.

The Squilla admin SPA owns auth, the sidebar, the dashboard, and the extension loader. That's it. Every feature page — Media Library, Email Manager, Forms, Sitemaps — is a React micro-frontend shipped by its extension and loaded via import maps from the running container.

Add an extension, see a new sidebar entry. Remove it, the entry vanishes. The shell never grows.
Browse extensions
tengo
// scripts/theme.tengo — seed taxonomy terms idempotently
taxonomies := import("core/taxonomies")
terms      := import("core/terms")

taxonomies.register({
    slug: "trip_tag", label: "Trip tag",
    label_plural: "Trip tags", node_types: ["trip"]
})

seed := func(slug, name) {
    existing := terms.list("trip", "trip_tag")
    for t in existing { if t.slug == slug { return } }
    terms.create({
        node_type: "trip", taxonomy: "trip_tag",
        slug: slug, name: name
    })
}

seed("foodie",     "Foodie")
seed("adventure",  "Adventure")
seed("relaxing",   "Relaxing")

Themes self-bootstrap. theme.tengo runs once on activation and again on every restart while the theme is active — existence checks make re-runs safe.

Things developers said while building with Squilla.

star star star star star

"I asked Claude to add a 'recipe' content type with categories and seed three pages. It made nine MCP calls and the site rendered. I went to make tea."

B
Backend dev
found out about MCP last Tuesday
star star star star star

"I docker-cp'd a new theme folder into a running container expecting to restart. The theme just appeared in the admin. I sat there for a minute."

O
Ops engineer
Squilla user since 2026-04
star star star star star

"The capability guard caught my extension trying to write settings before I declared settings:write. The error message told me which line of extension.json to edit."

E
Extension author
shipping their first plugin

Questions you probably have

Why not just put everything in core? expand_more

Because then the CMS becomes the thing you fight when your needs change. Squilla's hard rule: if removing a feature would leave dead code in core, that code belongs in an extension. Media, email, forms, even content blocks — all opt-in. Disable what you don't need; the kernel stays the same size.

What does "hot drop-in" actually mean? expand_more

Drop a theme or extension folder onto the filesystem of a running container — via docker cp, a volume mount, a git pull — and an fsnotify watcher picks it up, debounces, and re-runs the loader scan. The new package shows up in core.theme.list / core.extension.list immediately. Activation is also hot: theme switches swap the asset registry atomically, extension activation spawns the gRPC plugin subprocess in milliseconds.

Do I have to write Go to build an extension? expand_more

No. Tengo-only extensions are a thing — see resend-provider, which is just a manifest plus one .tengo file that subscribes to email.send and shells out via core/http. Reach for a Go binary when you need owned database tables, large request bodies, or native libraries.

Where do I start? expand_more

Call core.guide from any MCP client. You'll get a goal→tool decision tree, a live snapshot of the CMS, and the full tool index in one payload. From there, core.theme.standards and core.extension.standards are the authoring contracts.

Drop a folder in. Watch it light up.

Squilla is open about how it works because the architecture is the product. The kernel is small, the contracts are public, and every behavior in this site was set up via MCP — including this homepage.

Open the admin