Spillway CMSStructured content like Drupal. Page building like Puck. Setup like Ghost. AI that drafts, people who approve.
Menu

Build a module with an agent

A recipe for having Claude Code, Cursor or any coding agent write a Spillway module: the prompt to paste, what the agent should produce, and the checks to run before you trust it. Read modules.md once yourself; the agent will read it too.

Decide the tier first

TrustedSandboxed
Runsin the server process (JavaScript)in a WebAssembly sandbox with declared capabilities
Canregister blocks, field types, admin pages, providers, AI tools; hook into saves and renders; page builder blocks rendered with React in blocks.jsrun beforeSave, beforeRender, afterX hooks and AI tools over JSON; call declared HTTP hosts; page builder blocks rendered as sanitised HTML through render_block
Registryhuman review before it is listedautomated checks, listed at once
Installneeds SPILLWAY_TRUSTED_MODULE_INSTALL=1 on the siteany admin

Ask for sandboxed whenever the module only validates, transforms or enriches content, or offers a block whose markup is prose (text, links, lists, images: the sandbox's HTML is sanitised to that). Ask for trusted when a block needs its own markup and classes, or the module needs an admin screen or a field type.

The prompt

Copy, fill the brackets, paste into the agent in a checkout of the module's own repository (or an empty folder with Node 22):

Build a Spillway CMS module named "[name]" (tier: [trusted|sandboxed]).

Purpose: [one paragraph: what it does, for whom, and what it must not do].

Follow the module contract in docs/modules.md and the ModuleManifest type in packages/shared/src/index.ts
of https://github.com/kwall-llc/spillway-cms (read both before writing code). Scaffold with
`npx spillway-module create [name] --tier [tier]` from @spillway-cms/sdk, then implement it.

Requirements:
- manifest.json: name, semver version 0.1.0, tier, description, a `spillway` range of "^0.1.0",
  the smallest set of capabilities the code uses ([entity.read, http:api.example.org, ...]),
  and only the hooks it handles.
- [Trusted] export default defineModule({ manifest, setup(api) }) from @spillway-cms/sdk; bundle every
  dependency into the entry file (there is no node_modules at runtime); escape every string you put
  into HTML; blocks are server-safe (no browser APIs).
- [Sandboxed] export memory, alloc and handle; stay under 32 MB of memory and 100 ms per call;
  only call hosts declared as http:<host>; abort with a message rather than throwing.
- [If it offers a page builder block] declare it under `blocks` in the manifest (key, label, fields of
  the kinds docs/modules.md lists, defaults) with the `blocks` capability; trusted: render it in
  blocks.js with the host's createElement (defineBlock/defineBlocks from @spillway-cms/sdk), escaping
  every string; sandboxed: answer `{ "hook": "render_block" }` calls with `{ "payload": "<html>" }`
  using only the prose markup the sanitiser keeps (no div, class, style or script).
- [If its block has looks to choose from] declare `"styles": "<core block>"` (reuse the brand's styles,
  e.g. "Cards") or `styleSlots` (its own kebab-case values) instead of a color or variant field, and
  render from `props.style` and `props.styleValues` (docs/modules.md, "Block styles in a module block").
- A README.md with: what it does, settings, capabilities and why each is needed, how to install.
- A CHANGELOG.md with a 0.1.0 entry.
- Tests that run with `npm test` without a Spillway server.
- No secrets in the repository; settings that need one read it from the module settings.

When you are done, list the files, the capabilities and anything you could not verify. Do not publish it.

What to check

  1. Manifest. Open manifest.json: the capabilities are the ones the code needs and no more; hooks lists only handled hooks; main points at the entry; the spillway range is right.
  2. Tests. npm test passes without a server.
  3. A real load. Start a local Spillway (pnpm start from a checkout, or the Docker image), copy the folder to <DATA_DIR>/modules/<name>/, restart, and enable it under System → Modules (for a trusted module, start the server with SPILLWAY_TRUSTED_MODULE_INSTALL=1 or enable from disk as an admin). lastError must be null and Logs clean. Trigger the hook (save or publish an entity, render a page) and check the effect.
  4. Sandbox limits (sandboxed): the smoke load passes; a hook that does real work stays well under the 100 ms budget on a sample entity.

    Blocks: GET /api/builder/config lists the block, dropping it in the page builder shows the server's rendering, and the public page renders it; disable the module and the page shows the placeholder instead of an error.

  5. Read the code once with the capabilities in mind: every outbound request goes to a declared host, nothing writes outside its settings, nothing logs content it should not.
  6. Spillway's own checks still gate everything: a beforeSave hook can abort a save with a message but cannot publish, and the module runs with the permissions of the request that triggered it.

Then hand it to a person to review, and publish it with publish.md when it is theirs to publish.

Examples to point the agent at

  • examples/modules/hello-banner (trusted: a block and a hook, plus a manifest block rendered by blocks.js).
  • examples/modules/profanity-guard (sandboxed: a beforeSave scanner built with the SDK's Wasm assembler, plus a render_block answer for its notice block).

Spillway 0.1.0 · Edit this page on GitHub

Was this page helpful?