
Declarative, declarative + code, or pure code, plus a private registry. Once installed, an extension shows up in the admin.
Sign in with a password or a passkey. Google and LINE sign-in come as extensions.
Next.js + OpenNext on Cloudflare Workers, D1 / R2 for data, responding close to everyone.
Three kinds of extension, from a single manifest file to a full package of code. Install one and it shows up in the admin — the core doesn't change.
One manifest.json is one feature: content types, admin pages and public routes, all described as data — the contact form on the right is a real example from the registry.
A manifest that also ships its own admin components and SQL, published and versioned together in the registry.
A whole package living in extensions/<id>/ — its own admin, public pages, API and kit, compiled into the core.
{ "kind": "declarative", "id": "contact", "coreApi": "^1.3.0", "contentTypes": [{ "name": "submission", "fields": [ { "key": "name", "type": "text", "required": true }, { "key": "email", "type": "text", "required": true }, { "key": "message", "type": "text", "required": true } ] }], "adminPages": [{ "view": "collection", "layout": "table" }], "publicRoutes": [{ "pattern": "/contact", "view": "form" }] }
Excerpt from the contact extension's manifest in the registry — a declarative form whose anonymous submissions land straight in shared content.
cms add fetches from the registry, materialises extensions/<id>/ and patches registry.ts. Private registries work over a token, and re-running is idempotent — no duplicate lines.
~/cms $ npx @sz.ws/cms add contact
registry — fetched manifest.json
materialised extensions/contact/
patched extensions/registry.ts
--dry-run shows what it would do; private registries use SZWS_REGISTRY_TOKEN.
A chat panel built into the admin, wired to your content, orders and settings. It can answer questions, draft changes and run real calculations — but every write comes back as a proposal. Nothing changes until you say yes.
A new post, a status change, a refund: every write becomes a proposal that waits for you to confirm it. There is no setting that skips this.
Ask how many orders came in this month and it draws the answer with the same chart components the dashboard uses, with numbers pulled straight from your data.
When numbers need calculating, the model writes code and runs it in a QuickJS sandbox inside a Web Worker.
The same components that ship in the admin. Select the text, hover the rows. A ⌘K search, the numbers filling in and the save bar rising and dropping away all loop on one shared timeline.
Writing your first extension