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

Getting started

This is the user manual for the admin. It starts with the setup wizard and the parts of the admin every screen shares, then takes the sidebar from top to bottom: one section per area, each with what it is, where it is, the most common tasks and links to the reference. It assumes Spillway is running at http://localhost:3000 (see install.md). For step-by-step recipes (publish a campaign page, connect HubSpot, set up SAML, translate a page, schedule a post, install a kit, build a page with AI, launch a campaign with a workstream, add a department section, import a website, import a design system from Figma) see the how-to guides; this page links to them rather than repeating them.

Your first ten minutes

A fresh site walks you through its first steps. After the setup wizard (which offers a kit as its last step), the dashboard shows a Getting started checklist to anyone who can change site settings. Each step is checked off by itself as the site changes, for every admin at once:

  1. Pick a kit or start blank. Install a kit for your kind of site (higher education, city, nonprofit, intranet), or choose Start blank to build your own content types. Done when a kit is installed or you chose to start blank.
  2. Create or edit your home page. Open the home page in the page builder and save a change. Done when the home page has more than one revision of your own (the revision a kit writes when it replaces the starter page does not count).
  3. Add your logo and colors. Pick your logo and set the primary color and fonts under Brand, then publish. Done when a theme token differs from the defaults, a brand version is published, a logo is set under Brand → Identity, or a logo is in the Media library (a file or title with "logo" in it).
  4. Invite a teammate. Add the people who write, review and publish under Users. Done when the site has more than one user.
  5. Connect your domain. Add the domain under Sites or set a public base URL. When Spillway runs from source rather than the Docker image, the step links the reverse proxy guide instead. Done when the site has a domain or a base URL that is not localhost.
  6. Set your SEO defaults. Write the default description or pick a social image under Site settings. Done when either is saved.
  7. Turn on AI help (optional). Add a provider key under AI. Done when a provider has a key; it does not count toward the total.
  8. Take the page builder tour. Five short coach marks in the builder (see the page builder tour). Done when you finish it; the tour is per person.

Every step has a one-line description, a primary action that opens the right screen (admin screens show a "From the getting started checklist" banner with the way back and highlight the control the step is about; the home page and tour steps open the page builder directly) and a How to link to its section of this manual. The ring at the top shows the share of the seven required steps that are done. The checklist goes away when every required step is done, or when you click Hide this: the notice that follows has Undo, and Help → Getting started checklist brings it back later. Hiding is per person. The state comes from GET /api/site/onboarding (OnboardingState in the API reference).

The setup wizard

Open http://localhost:3000/admin. Until the instance has a user, every admin URL redirects to /admin/setup.

Fill in:

  • Site name
  • Your name
  • Email
  • Password (at least 8 characters) and confirmation
  • Setup token, only when the server was started with SETUP_TOKEN set (the field appears by itself; paste the value whoever started the server gave you)

"Create site" does the following in one transaction: creates the site settings with the default theme, your user with the admin role, the two built-in content types (Page and Post), and a published sample home page, then signs you in with a "Welcome to Spillway. Your site is ready." notice. The public site at http://localhost:3000/ now shows that home page.

The wizard's last step asks Install a kit now? and lists the bundled kits with their descriptions and what each adds (types, pages, forms). Include sample content adds example entries you can edit or delete. Click Install on a kit to install it and open the dashboard, Start blank to record that you will build your own types, or Skip for now to decide later from the checklist; nothing here is required. A kit installed here replaces the untouched sample home page with its own. Kits can be installed at any time under Kits.

The wizard can only run once. A second POST /api/setup returns 409. If you created the first admin from the terminal instead (pnpm spillway create-user, see install.md), the wizard is skipped and /admin shows the login screen; the site is named "Spillway" unless you passed --site-name, and you can change it under Site settings.

Roles and the review workflow

Each user has one or more roles. Permissions add up.

RoleCan
viewerSee drafts and previews.
authorCreate and edit own content, submit it for review, upload media.
editorEdit, publish, unpublish and delete any content; delete media; view form submissions.
managerEditor, plus content types, site settings, forms, menus, kits and sandboxed modules.
adminEverything, including users, sites and trusted modules.

Statuses: an entity is a draft when created. An author who is done clicks Submit for review, which sets it to review and makes it show up in the Review queue and under "In review" on the dashboard. A reviewer (anyone who can publish: editors and up) opens it, checks the preview, and clicks Publish; the current revision becomes the live one and the public page updates immediately. Unpublish takes it off the site and returns it to draft. Archived is reached by a scheduled unpublish or the Archive bulk action.

Editing a published entity does not change the live page. The new revision stays unpublished (the action bar shows Publish again) until someone publishes it. Authors can only edit what they created; editors and up can edit anything. Roles can also be granted per site and per section on a multisite instance; see Users. The full permission list is in reference/permissions.md.

The admin at a glance

The sidebar has a primary group (Dashboard, Review queue, Analytics) and the groups Content (one entry per content type, then Media, Translations, Workstreams, Collections, Shared blocks, Vocabularies, Alerts), Marketing (Forms, Redirects, Experiments, Audiences), Structure (Content types, Menus, Brand, Kits, Import), Registry (only on a registry host) and System (Site settings, Users, Modules). Entries you lack the permission for are hidden; a section editor sees only their section's content and menu. The sidebar collapses to icons, and below 900 px it becomes an off-canvas drawer.

The top bar holds the site switcher (when you can open more than one site), the workstream switcher, View site (the public site in a new tab), the Help menu, the notification bell (review requests, returned drafts and today's schedule) and the account menu: API tokens, My preferences (email notifications for review activity and comments), Interface language, the light, dark or system theme, Keyboard shortcuts and Log out. Ctrl/Cmd+K opens the command palette: every screen you may open, actions (New page, Upload media, Invite a user, switch site or workstream, toggle the theme) and a content search.

The Help menu

The question mark in the top bar opens Help, the same on every screen:

  • Manual: this page, on the documentation site, in a new tab.
  • How-to guides: the guides section of the documentation.
  • Keyboard shortcuts (also ?): every shortcut the admin has. Ctrl/Cmd+K opens the command palette, / searches, [ collapses the sidebar, ? opens the list; the arrow keys move through lists, the sidebar, menus and the steps of the tour, Enter opens the focused row, Escape closes a dialog, a menu or the tour. In the page builder: Ctrl/Cmd+Z undoes, Ctrl/Cmd+Shift+Z or Ctrl/Cmd+Y redoes, Delete or Backspace removes the selected block.
  • Replay the page builder tour (with content.create): opens the home page, or your most recent page, in the builder with the tour running.
  • Getting started checklist (with site.settings): brings a hidden checklist back to the dashboard.
  • Status page: the instance's /status page in a new tab.
  • Report a problem: a new issue on the project's GitHub repository.
  • The running version (for example Spillway v0.1.0), to quote in a report.

Lists and saved views

Every list (content, media, review queue, users, forms and the rest) is a table with sortable columns, a column chooser, a density switch, a search box, filter chips and a checkbox per row for bulk actions. The Views menu next to the search box saves a list setup under a name: set the filters, the sort, the visible columns, the density and the rows per page, open Views → Save current view…, name it, and it is yours on every browser you sign in from (stored per user through PUT /api/me/preferences/views:<table>). Pick a view from the menu to apply it; the active one is marked. With a view active, the menu offers Set as default (applied whenever you open that list without filters), Delete and, once you change something, Update. Reset to defaults clears everything. The menu is keyboard operable (arrow keys, Enter, Escape).

Shared views. Managers and admins (views.share) can share a list setup with the team: Views → Share current view…, name it, choose Everyone on the site or Only these roles (Viewers, Authors, Editors, Managers, Admins), and tick Open the screen with this view for them to make it their default. The people it is shared with find it in their Views menu under Shared views, labeled with who it is for, and can apply it or choose Set as my default. Your own default always wins; without one, the list opens with the shared default for your roles (a view for named roles wins over one for everyone). With a shared view active, managers also get Update shared view after a change, Edit sharing…, the default switch for its audience and Delete shared view…, which removes it for everyone. Shared views belong to the site you are on.

An empty list says what belongs there and offers one primary action (New page, Upload files, New form, New collection, New menu, Invite a user) with a Read the manual link to its section here. The Users list is never empty, since you are on it; while you are the only user it shows Just you so far with the same invite action.

Editors and unsaved changes

Every editor (entity, content type, form, collection, menu, the page builder) has a sticky action bar and warns before you lose work: following a sidebar link, a breadcrumb, the palette or the site and workstream switchers with unsaved changes opens an Unsaved changes dialog (Discard changes / Stay); a reload or closed tab gets the browser's own prompt.

Dashboard

What it is. The landing screen after sign-in: a greeting, a Top tasks list of shortcuts you have permission for (New page, New post, Upload media, Invite a user, Site settings), a table of content per type and status (Draft, In review, Published, Archived), a Waiting for review card with the count and the five oldest items, the Scheduled items, Traffic, last 7 days from the built-in counter, Docs feedback (only when the site has a form with the key docs_feedback, as the docs kit creates, and you may view submissions: the Yes and No counts of the last 30 days, the helpful share and the last five comments with the page and version they came from, linking the form's submissions), and Recent activity (the ten most recently updated entities).

Where. The first sidebar entry; also the breadcrumb root of every screen.

On a new site, people who can change site settings also see the Getting started checklist at the top (see Your first ten minutes).

Common tasks. Jump to a type's list from the status table; open the oldest item waiting for review; start a new page or post from Top tasks.

Review queue

What it is. Every entity that was submitted for review, oldest first, with its type, author, how long it has been waiting and how many comments it has. A Scheduled tab lists everything with a pending publish or unpublish time, soonest first, with any error from the last attempt.

Where. Sidebar, primary group, for anyone with content.publish (editors and up). The badge shows how many items are waiting.

Common tasks.

  1. Click a row to open the item in the editor. A reviewer has two extra actions on an item in review: Publish and Reject. Reject asks for a note, returns the item to draft and records the note on the new revision as "Rejected: …", so the author sees it in the Revisions panel.
  2. Read the Comments and Checks panels in the right rail before publishing (see Checks and comments); errors block publishing unless you choose Publish anyway.
  3. Open the Scheduled tab to see what goes live when, and fix an item whose last scheduled publish failed (the badge turns red with the message).

Reference. api.md, Review; the guide Publish a campaign page.

Analytics

What it is. A cookie-free traffic counter built into the server: views and unique visitors per day, the top pages, referrers and campaign sources, and the conversions recorded by forms and experiments. Nothing identifies a visitor and bots are left out, so it needs no consent; it sits beside the GTM, GA4 or Plausible tags from Site settings → Integrations rather than replacing them.

Where. Sidebar, primary group, for anyone who can publish or manage settings. The dashboard shows the last seven days.

Common tasks. Pick 7, 30 or 90 days or any date range; hover the chart for exact numbers or switch it to a table; click a page in the top pages for its daily views.

Reference. personalization.md, Analytics; api.md.

Content: pages, posts and your own types

What it is. The Content group lists every content type (Pages and Posts after a fresh install, plus the types you create under Structure → Content types or install with a kit). Each entry is a list of that type's entities; every entity opens in the same editor: fields on the left, a sticky action bar (Save draft, Submit for review, Publish, Unpublish, Preview, Schedule…, Clone, and inside a workstream Save to workstream), and a right rail with the URL, Checks, Comments, Revisions and Info.

Where. Sidebar → Content → the type (Pages, Posts, …). New <type> sits at the top of each list and in the command palette.

Common tasks. Create a page in the page builder, write a post in rich text, and publish through review: the three walkthroughs below. Then: preview a draft, compare and restore revisions, schedule a publish, run the checks, act on many items at once.

Your first page in the page builder

  1. Click Pages in the sidebar, then New page.
  2. Type a title. The slug is generated from it (About us becomes about-us) until you edit the slug yourself; the path preview under it shows the public URL (/about-us).
  3. Click Save draft. The page now exists as a draft with revision 1.
  4. The Layout field shows an Open page builder button (the field itself is edited in the full-screen builder at /admin/content/page/<id>/layout).

In the builder:

  • The left panel lists the blocks in three groups: Layout (Columns, Spacer, Divider), Content (Heading, Text, Image, Embed, Button) and Marketing (Hero, Call to action, Cards, Form, Collection, Shared, Calendar, Personalize, Experiment, and the registry blocks), plus a group for the blocks installed modules offer (Modules, or the group the module names). Drag one onto the canvas. Every block arrives with sensible defaults so it looks finished.
  • Click a block to edit its fields in the right panel. Heading text can also be edited directly on the canvas.
  • Style: Hero, Cards, Button, Call to action, Heading and Text (and module blocks that declare styles) start with a Style field: the site's named styles for the block, each shown as a small picture of the block in that style. Pick one by name; there are no color fields, so the page stays on brand. The default is marked (default) and a block without a style uses it. You see only the styles your role may use; a block that already has a style limited to another role shows it read only, with a note. Managers set the styles up in Brand → Styles.
  • Slots: the Columns block has two to four columns, each an empty slot. Drag blocks into a column. Columns stack on phones. Personalize and Experiment blocks have slots too.
  • The root (click the page background) has a Page title field. When you save from the builder, a non-empty root title replaces the entity's title.
  • Share on a block's action bar saves it to the shared block library.
  • Save draft writes a new revision with the note "Layout edited in page builder". Publish (only shown to users with content.publish) saves and publishes in one go.
  • Back returns to the entity form; unsaved layout changes get the Unsaved changes dialog.
  • The rail at the far left switches the left panel between Blocks and Outline (the page's blocks as a tree, where you can select, duplicate or delete one). The header's panel buttons show and hide the left and right panels.
  • Keyboard: Tab reaches the header, the rail, the blocks list, the canvas toolbar, the canvas and the fields; Enter or Space on a rail item switches the panel. Select a block from the keyboard in the Outline, then edit its fields. Adding and moving blocks still needs a pointer (drag and drop); see the open decision.
  • Narrow screens and phones: below about 1180 px the header shows only the page title and Back, Build from brief and Preview become icons; below 900 px the actions move to a second row. Below 638 px the builder switches to its phone layout: the canvas fills the screen and the rail at the bottom opens Blocks, Outline and Fields as a panel under the canvas, with the header still above it.

See blocks.md for what each block does.

Back on the entity form, click Publish. The page is live at its path, for example http://localhost:3000/about-us. View site in the top bar opens the public site in a new tab.

Every save adds an entry to the Revisions card in the right rail, with the author, time, note, and "Current" and "Live" badges.

The page builder tour

The first time you open the page builder, a short tour starts by itself: five coach marks that point at the block list, the canvas, the properties panel, Publish (or Save draft when you cannot publish) and Preview (which opens the last saved draft in a new tab). Each is a small dialog: Next and Back move between steps, Skip tour or Escape closes it, and the arrow keys, Home and End work too. Focus stays in the tour while it is open and returns where it was when it closes.

The tour does not start again once you have finished or skipped it. Replay it from the question mark button in the builder's header or from Help → Replay the page builder tour. Finishing it once checks off the last step of the getting started checklist; skipping a replay never undoes that. Whether you have seen it is stored per person (onboarding:tour under /api/me/preferences).

Writing a post

Posts, then New post. The built-in Post type has Title, Summary, Body (rich text), Hero image (media) and Publish date.

  • Body is a rich text field (below). The value is stored as sanitized HTML.
  • Hero image opens the media picker: choose an existing file, upload one, or pick one from a connected DAM.
  • Save draft, then Publish. The post is at /blog/<slug>.

To publish at a set time instead, see Scheduling and the guide Schedule a post.

Rich text editing

Fields of type richtext open a Tiptap editor. The toolbar has a block type menu (paragraph, headings 2 to 4), bold, italic, underline, strikethrough, Link, Image, bulleted and numbered lists, quote, code, horizontal rule, Table (with row and column buttons once the cursor is in a table), undo, redo, and the AI assist menu.

Link opens a dialog with two tabs. Link to content searches your pages and posts by title and inserts a link that remembers the target, so it keeps working when the page's slug changes and is dropped automatically if the page is unpublished. External URL takes any https:// address. Image opens the media library and embeds the chosen image; on the published page it is served responsively with a focal-point-aware crop.

Whatever is pasted in is cleaned on save: scripts, styles and event handlers are removed, unsafe links dropped, and headings outside h2 to h4 reflowed. See content-model.md, Rich text.

AI assistance

With a provider configured under Site settings → AI and the ai.use permission: Draft with AI on a new page turns a brief into a draft layout built from real blocks, the Assist menu on any text field rewrites, shortens, expands, changes the tone, fixes, translates or summarizes, and the media details panel can suggest alt text for an image. Everything stays a draft until a person publishes. See the guide Build a page with AI and Site settings → AI (the brand voice the AI follows is under Brand → Voice).

Previewing drafts

The Preview button on the entity form opens /_preview/<id> in a new tab. It renders the latest revision whatever the status, with noindex and no canonical link, and is never cached. It requires a session with content.view_drafts, which every role has; anonymous visitors get a 401 error page. Inside a workstream the preview shows the branch version.

The public URL (/<path>) always shows the published revision, or 404 when there is none.

Revisions, restore and compare

The Revisions panel lists every save, newest first, with the author, time, note and the "Current" and "Live" badges. Restore on any older revision copies it into a new draft revision. Tick two revisions and click Compare: the dialog shows every field that differs between them side by side, text fields with word-level highlighting (deleted words struck through in red, inserted words in green), rich text compared as plain text, other values as before and after. For pages built in the page builder, the layout is compared block by block: which blocks were added, removed or changed. Unchanged fields are left out.

Revision history is trimmed on every save to the number set in the Content card of Site settings → Site (Revisions to keep per item, default 50). The live revision, the current one and any revision a comment was written against are always kept, whatever the number. spillway prune-revisions [--keep N] applies the rule to everything at once, for example after lowering the setting.

Checks and comments

Checks runs the editorial checks on the latest revision when the editor opens and again after every save (and on Re-run): required fields, SEO title and description length, images without alt text, heading order in the layout, banned words from the brand profile and internal links to unknown paths. Each result is an error or a warning with a message; Go to <field> scrolls to and focuses the field, and layout problems link into the page builder. Errors block publishing: clicking Publish then shows the failing checks in a dialog. Someone with the publish permission can still choose Publish anyway, which publishes the revision with the checks overridden; everyone else fixes the content first.

Which checks are errors: a missing required field and an Image block with a picture but no alt text. Everything else is a warning: SEO title outside 10 to 60 characters or description outside 50 to 160, no or several H1s and skipped heading levels (counted across Hero titles and Heading blocks), banned words from the brand profile, and internal links to paths that are neither a published page nor a redirect. The full list with ids is in api.md.

Comments is a thread on the entity. Anyone who can see drafts can post; a comment can be about the whole entity or about one field (chosen from the drop-down next to the Post button), in which case the field name appears as a tag that jumps to the field when clicked. Resolve marks a comment done and hides it behind "Show resolved"; Reopen brings it back. You can delete your own comments; editors and up can delete anyone's. The review queue shows the comment count per item so reviewers know where the discussion is.

Scheduling

Anyone who can publish can set a schedule on an item from the Schedule… button in the action bar: a Publish at time (for drafts and items in review) and an optional Unpublish at time, both in your local time zone. The action bar then shows a badge ("Publishes 12 Jan 2026, 09:00") with a Cancel link; the content list shows a scheduled badge next to the status and has a Scheduled chip that filters to everything with a pending time, and the review queue has a Scheduled tab with the same list, soonest first.

The status does not change until the time comes: a scheduled item stays a draft (or in review) and can still be edited. A ticker in the server runs every 30 seconds and publishes what is due through the normal publish path, so the pre-publish checks apply, the audit log records it (as the system actor) and cached pages are evicted. If a check reports an error at that moment, the item is left scheduled, the error is kept on it (the badge turns red and the Scheduled tab shows the message) and a schedule.failed notification goes out; fix the content and the next tick publishes it. Unpublish at moves a published item to Archived, which takes it off the site.

Hosts that run several instances, or that want cron to own the clock, start the server with SPILLWAY_TICKER=0 and run spillway run-scheduled from cron; it does one pass of the scheduler (due entities, then scheduled workstream merges) and the notification outbox. Step by step: Schedule a post.

Editing locks

Opening an item in the editor takes an editing lock on it. While you have it, the editor sends a heartbeat every minute; the lock lapses 15 minutes after the last heartbeat, so a closed laptop never blocks a page for long, and it is released when you leave the editor. If someone else holds the lock, a banner says so ("Kevin is editing this page since 10:21"), the form is read-only and a save is refused. Editors and up see Break lock on the banner, which takes the lock over; the other person's next save then fails with the same banner, so nothing is silently overwritten.

Bulk actions and clone

Every content list has a checkbox per row and one in the header. With rows selected, an actions bar offers Publish, Unpublish, Archive, Submit for review, Change author and Delete, each limited to what your role allows; the server checks every item on its own and the toast says how many went through and why the rest did not (a draft whose pre-publish checks fail is left as it is, for example). In the editor, Clone in the Info panel makes a draft copy ("Copy of …") with a fresh URL and opens it.

Events and date ranges

A content type can have a Date and time field (type daterange). The widget takes a start, an optional end, a time zone (defaults to yours) and an "All day" switch, plus a Repeat rule: daily, weekly (pick the weekdays), monthly or yearly, every n units, ending on a date or after a number of times, with dates to skip. Under the rule the editor previews the next five occurrences so a "every second Tuesday until May" reads back as real dates. Times are kept in the event's own zone, so a 10:00 weekly meeting stays at 10:00 across the daylight-saving change.

When an entity is saved, every occurrence (from the start date to two years ahead) is written to a table that listings and calendars query; see Collections for calendars and feeds. Run spillway sync-occurrences monthly from cron on long-lived sites so the two-year horizon keeps moving.

Reference. content-model.md for every field type and what is stored; api.md, Entities and Editorial.

Media

What it is. The media library: uploaded files, their alt text and titles, focal points, responsive derivatives, and assets that live in a connected DAM.

Where. Sidebar → Content → Media. media.upload (authors and up) lets you upload and edit alt text; media.delete (editors and up) lets you delete. The media picker in entity fields and the rich text editor opens the same library.

Common tasks.

  1. Upload. Drag files onto the drop zone or use Upload files; search by file name or alt text. Click a file to open the details panel: alt text ("Describe the image for people who cannot see it"), title, Copy URL, Delete.
  2. Set the focal point. Open an image, click the subject in the preview (or focus the preview and use the arrow keys), watch the 16:9 and 1:1 crop previews update, then Save. Every cover crop the site generates for that image keeps the point in frame: hero backgrounds, card thumbnails, ?fit=cover derivatives. Reset returns to the center. The Sizes list under the preview links to ready-made widths (480 to 1920 px) of the image.
  3. Use a DAM asset. With a provider connected (below), the picker and the library get a From Cloudinary (or Bynder, Brandfolder) tab: search by text and kind, then Use this.

Facts worth knowing:

  • Files are limited to 25 MB.
  • The type is detected from the file's bytes (PNG, JPEG, GIF, WebP, PDF, SVG) rather than trusted from the name; width and height are recorded for raster images.
  • Files are stored at <DATA_DIR>/media/<id>/<filename> and served at /media/<id>/<filename> with a one-year cache lifetime. SVGs are served with a sandboxing CSP.
  • Raster images can be requested at any size: /media/<id>/<filename>?w=800&h=600&fit=cover|contain|inside&fmt=webp|avif|jpeg|png&q=80. The derivative is generated on first request with sharp (cropped around the image's focal point for cover, never enlarged for inside/contain, capped at 4000 px), cached under <DATA_DIR>/media/<id>/_v/ and served with the same immutable headers; unknown parameters are ignored. MediaItem.variants lists ready-made widths.

Digital asset management (DAM)

If your images live in Cloudinary, Bynder or Brandfolder, connect it under Site settings → Media (site.settings): pick the provider, enter its credentials and, optionally, a default folder that narrows every search (a Cloudinary folder, a Bynder tag, a Brandfolder section). Test connection checks the credentials before and after saving; the secret ones are stored encrypted (with the instance secret under <DATA_DIR>/ai-secret) and shown as ******** afterwards, and leaving the mask in place keeps the stored value.

Per provider:

  • Cloudinary: the cloud name, an API key and its API secret from the console (Settings → API keys). Search runs on the Search API, so the default folder is a Cloudinary folder (marketing matches marketing/*). The API secret also verifies notifications.
  • Bynder: the portal domain (acme.bynder.com) and a permanent token with media read permission (Settings → Advanced settings → Permanent tokens). The default folder is a tag. Delivery uses the asset's dynamic transformation URL when the portal has Dynamic Asset Transformations, else the webimage derivative; the original is downloaded only on Copy to Spillway and only when the token may read it.
  • Brandfolder: the Brandfolder id and an API key (your profile → Integrations → API keys). The default folder is a section. Delivery uses the attachment's CDN URL.

Use this on an asset creates a media item whose file stays in the DAM: it shows a provider badge in the grid and the list, its details panel names the Source with a link to the asset, and its alt text and title are taken from the DAM's metadata (Cloudinary context alt/caption, Bynder description and name, Brandfolder description and name) so you can edit them like any other item. Importing the same asset again returns the existing item.

How delivery works: the item keeps a normal /media/<id>/<filename> URL, so blocks, rich text and media fields treat it like any file, but pages are rendered with the DAM's own delivery URL for every srcset candidate, in the provider's transformation syntax (Cloudinary c_fill,w_,h_,g_xy_center for focal crops and f_webp; Bynder ?io=transform:fill,width:,height:&format=; Brandfolder ?width=&height=&fit=crop&crop=focalpoint&fp-x=&fp-y=&format=). A request to the /media/... URL answers with a redirect to the same delivery URL, derivative parameters translated. Nothing is downloaded through Spillway, no derivative is generated with sharp, and the focal point set in Spillway is sent to Cloudinary and Brandfolder (Bynder crops around its own focus point). Non-image assets (PDFs, video) are delivered as the original.

When to copy locally: Copy to Spillway in the details panel (media.upload) downloads the asset through the SSRF guard under the 25 MB upload cap, stores it with the other uploads and clears the source; the URL and the file name stay, derivatives are generated with sharp from then on, and the pages using it are re-rendered. Do that when the site must keep working if the DAM subscription ends, when the asset is deleted or restricted in the DAM, or when you want Spillway's own resizing (for example a format the provider does not transform).

Webhooks: the Webhook card in Site settings → Media shows the URL to register with the provider (/api/media/dam/webhook/<provider>). Cloudinary notifications are verified with the API secret (X-Cld-Signature); Bynder and Brandfolder do not sign theirs, so append ?token=<webhook token> to the URL you register (the token is a credential of the connector). When an asset changes, every item that references it gets its URL, dimensions and version refreshed from the DAM (or just a new version when the DAM no longer answers for it), so every delivery URL changes and the pages using it are evicted from the cache through the media:<id> tag. A deleted asset keeps its item, versioned, until someone replaces it or copies something else in.

Reference. api.md, Media and DAM connectors; blocks.md, Image for the responsive markup.

Translations

What it is. The languages a site publishes in, the translation of each entity and the jobs sent to a translation management system (TMS). Languages themselves are configured under Site settings → Languages (below); this screen is where you see what is missing or outdated and send work out.

Where. Sidebar → Content → Translations; shown once the site has more than one language. Reading needs content.view_drafts; sending to a TMS needs content.translate (editors and up).

Common tasks.

  1. Translate an entity. With more than one language, every editor shows a language strip under the action bar: one tab per language, the open one highlighted, the source badge on the original, and current or outdated on each translation (outdated means the source changed since the translation was last saved). A language with no translation yet shows as missing; its tab opens Add translation with three choices: Start empty (title and shared fields only), Copy from source (everything, to edit in place) and Translate with AI (text fields and the copy inside the page layout, on brand; only with the AI permission and a configured provider). The new draft opens at once; after an AI translation the fields the model wrote carry an AI translated badge and a notice asks you to read them against the source. Fields marked as shared (media, dates, references, numbers, tags by default) are read-only on a translation with a hint: edit them on the source and they are copied to every translation on save. The rail shows the public, locale-prefixed path (/es/about) and a View live link once published; the content list gains a Language filter and column. Step by step: Translate a page.
  2. Find what is outdated. The Translations screen shows, per language, how many items are up to date, outdated or missing, and lists the outdated and missing ones with a link that opens the translation or the source with the Add translation menu ready. Menu items get a label field per extra language in the menu editor.
  3. Send pages to translation. Site settings → Languages → Translation management system picks how translations are produced: by hand as XLIFF files, or through Crowdin, Phrase TMS or Smartling with a project id and credentials (write-only, with a Test connection button and, for the API providers, a webhook secret and the URL to register). On the Translations screen, Send to translation takes the selected rows (or a search, or the page an editor came from), the target languages and an optional due date. The Translation jobs table below shows each job's status; Pull fetches finished translations from an API provider, the manual provider offers XLIFF (download the bundle) and Upload (the translated files back), and the report lists what was applied, skipped or unmatched. Translations land as drafts in each language, never published.

The admin's own language is chosen in the account menu (Interface language); it is saved on your account and the shell's navigation follows it.

Reference. localization.md, including translation management systems; api.md, TMS.

Workstreams

What it is. A workstream is a branch of content changes (pages, posts, menus, site settings) you edit together, preview as a whole site on a private URL, compare with the live site and merge or discard in one action, now or on a schedule.

Where. Sidebar → Content → Workstreams lists every branch with its status, owner, item count, scheduled merge and last merge error, filtered by status (content.view_drafts). The workstream switcher in the top bar (next to the site name) lists the open workstreams, offers New workstream (a label, an optional key and a description) and Exit workstream.

Common tasks.

  1. Edit inside a workstream. Pick one in the switcher, or choose Edit in workstream in an editor on the main line. While one is selected, every call carries it, a banner at the top of every screen reads Editing in: (label) · Preview · Open · Exit, the editor's primary button becomes Save to workstream, Publish, Submit for review, Schedule, Unpublish, Reject and Restore are hidden (the merge is how a branch goes live), the slug and language are read-only, and the action bar shows in workstream or created in workstream badges. Content lists mark the rows a workstream changes and the pages it created.
  2. Review it. The detail screen holds the label and description, the items (kind, title, action, author, updated) with Compare per item, which opens the revision diff between the live revision and the branch one, a diff summary (field and layout changes, pre-publish checks), a conflicts panel (the live version changed or was deleted after the branch edited it), Preview (the whole site under /_preview/ws/<id>/) and Share link (a 7-day link for stakeholders without an account, with a copy button).
  3. Merge, schedule or discard. Schedule (a date and time for the merge), Merge (editors), Discard and Delete, each with a confirmation. A merge blocked by conflicts lists them and offers Merge anyway; a merge blocked by failing checks lists them and offers Publish anyway to editors.

Step by step: Launch a campaign with a workstream.

Reference. workstreams.md; api.md, Workstreams.

Collections

What it is. A collection is a saved query over one content type: "the five latest posts", "case studies tagged healthcare". A page shows one through the Collection block (cards, list or grid, with optional visitor filters and a pager) or the Calendar block, and every collection has iCal and RSS feeds.

Where. Sidebar → Content → Collections, for managers and admins (types.manage). The collection's items are also public JSON at /api/collections/<id>/items.

Common tasks.

  1. Build one. Basics: a key (machine name), a label and the content type the collection draws from (changing the type clears the filters, because the fields differ). Filters: each row is a field, an operator and a value. The field menu lists the system fields (title, status, author, published/created/updated dates) plus the fields of the chosen type (layout, JSON and rich-text fields are left out). The value control follows the field: a text box, a number box, a date-time picker, a drop-down for select fields, true/false for booleans. Operators are equals, does not equal, is one of (comma-separated values), contains, after/greater than, at least, before/less than, at most, exists (has a value / is empty) and, for taxonomy fields, has term (or a child of it). Rows with an incomplete value show an error and are left out of the preview until fixed. Order and size: sort field, direction and a limit between 1 and 100. Preview on the right runs the query as you type and shows the matching items. Use in page builder shows the collection id once it is saved, with a Copy button.
  2. Put it on a page. In the page builder, add a Collection block and paste the id into its "Collection id" field; the block offers cards, list or grid display. Only published entities are listed on the public site; the preview may show drafts if you filter for them. Publishing a new entity of the collection's type clears every page that lists that type, so a "latest posts" block is never stale. In the page builder the block shows a placeholder; the server fills in the items (title, link, summary, hero image, date) on the published page.
  3. Add visitor filters. The Collection block has a Visitor filters list. Each row names a field, a label and a kind: Keyword search (title and the type's text fields), Taxonomy term or Select (a drop-down; options come from the vocabulary, the select's options or the values in use), or Date range (from/to, matched against occurrences for date-and-time fields). Set Items per page, Show pager and Show result count, and the server renders the form, the count and the pager into the page: a plain GET form that works without JavaScript, keeps unrelated query parameters (a utm_source, say) in hidden fields, and marks the current page for assistive technology. Filtered pages are cached per query string for a minute and, like every page with a collection, evicted when an entity of that type is published.

Calendars and feeds

Collections can filter and sort on occurrence.start (now, now+30d and ISO dates are valid values) and, with expand: occurrences, list a recurring event once per occurrence; the higher-education kit's upcoming_events collection does exactly that. On a page, the Calendar block shows a collection as a month grid (with previous/next links, ?month=2027-03 picks the month) or as a date-grouped list, and links to the collection's iCal feed. Every collection has two feeds: /feeds/<key>.ics (one event per entity with the repeat rule, so subscribers' calendars expand the series themselves) and /feeds/<key>.rss.

Reference. api.md, Collections and Events, listings and feeds; blocks.md, Collection and Calendar.

Shared blocks

What it is. A library of blocks reused across pages: edit the original once and every page that places it updates.

Where. Sidebar → Content → Shared blocks (types.manage); in the page builder, Share on any block and the Shared block in the Marketing group.

Common tasks. In the page builder, select a block and click Share in its action bar; give it a label and a key; leave Use the shared copy here on to replace the block on the current page with a Shared block pointing at the library. Drop a Shared block on any page and pick a library entry from its field. Edit the original under Shared blocks (rename, delete, see how many pages use it); the pages that use it update as soon as they are next requested.

Reference. content-model.md, Shared blocks; blocks.md, Shared.

Vocabularies

What it is. Vocabularies hold the terms that taxonomy fields pick from: topics, departments, audiences. A vocabulary can give every term a public listing page.

Where. Sidebar → Content → Vocabularies, for managers and admins (types.manage).

Common tasks.

  1. Create one with a label, a key and, if you want a public listing page per term, a term page pattern such as /topics/{slug} (or /topics/{path} to include the parent slugs).
  2. Manage the tree. Add a term at the root or under a parent, Rename (label, slug, description), and reorder with the arrows: up and down among siblings, → to indent under the previous term, ← to outdent. Each term shows its path (a link when term pages are on) or its slug. Delete warns how many items are tagged with the term and moves its children up a level.
  3. Use it on a type. Add a field of type taxonomy to a content type and choose the vocabulary in its details; tick Multiple to allow several terms. The entity editor then shows a searchable tree picker. Collections gain the operator has term (or a child of it), and a term page lists everything tagged with the term or its descendants.

Reference. content-model.md, Taxonomy.

Alerts

What it is. Banners shown above the header on every page: a title, a message, an optional link, a level (info, warning or emergency, which spans the full width in red), an optional time window and whether visitors may dismiss it. A dismissed alert stays hidden on that browser for a week.

Where. Sidebar → Content → Alerts, for anyone who can publish.

Common tasks. Post an emergency alert with no end time and take it down by hand; schedule an info banner with a window; let visitors dismiss a marketing banner. Alerts are fetched by a tiny script from /_alerts after the page loads, so pages stay cacheable and an emergency shows at once on every page, cached or not.

Reference. api.md, Alerts.

Forms

What it is. Lead-capture forms with conditional fields, steps, file uploads, spam protection and CAPTCHA, their submissions, and connectors that send each submission on (webhook, HubSpot, email).

Where. Sidebar → Marketing → Forms: managers and admins build forms (forms.manage); editors view submissions (forms.view_submissions). The CAPTCHA provider for every form is under Site settings → Forms.

Common tasks.

  1. Build a form. Basics: key (used by the Form block and the public submit URL), label, submit button label, success message, and Capture marketing context, which stores utm_* parameters, the referrer and the landing page with every submission. Fields: a table like the content type editor's, with key, label, type (text, email, tel, textarea, select, checkbox, number, hidden, file), a required checkbox, and More for the placeholder, help text and, for select fields, the options; rows can be reordered with the arrow buttons and removed; keys must be unique and cannot start with an underscore (those are reserved for spam protection). The same panel holds Show this field only when…: pick another field, an operator (equals, does not equal, contains, is empty, is not empty, is greater than, is less than) and a value, and the field appears only while the rule matches; rules chain (a field that depends on a hidden field is hidden too), run in the browser as the visitor types and again on the server, and a hidden field is never required and never stored. A file field takes the accepted types (extensions such as pdf or MIME types such as image/*; empty means any type except executables and browser-run files), the largest file in MB (up to 25) and how many files (up to 10). Split into steps turns a long form into pages: give each step a title and tick the fields it shows; fields in no step land on the last one. Visitors see a progress indicator and one step at a time, each checked on the server before the next; without JavaScript every step is shown at once and checked on submit.
  2. Connect it. Submissions are always stored in Spillway; connectors also send each one somewhere else. Webhook takes a URL and an optional secret (the request is signed in X-Spillway-Signature with HMAC-SHA256). HubSpot takes a portal id and form GUID, plus a field map from each form field to a HubSpot field name; leave one empty to skip that field. Email takes a comma-separated list of recipients and uses the SMTP settings when configured, otherwise the message is logged. Step by step: Connect HubSpot to a form.
  3. Put it on a page and read the submissions. The Embed card tells you what to do next: add a Form block to a page and set its form key. The block renders a real HTML form that works without JavaScript, with an inline success message when JavaScript is available. Submissions (from the form's page header or the "Submissions" link in the forms list) is a table with the date, a status badge (Received, Delivered or Failed), the first three fields, the page the visitor submitted from and the UTM source. Click a row to expand it: the full data, the Files a visitor attached (each a download link; the files live in a private folder, never in the media library or at a public URL, and only people who may view submissions can fetch them), the context (page, referrer, IP, user agent, consent, CAPTCHA provider when one verified it, every UTM value) and the delivery log per connector with any error message. Redeliver appears on failed submissions and runs the connectors again; Delete asks for confirmation and removes the files too. Download CSV streams every submission as a spreadsheet. The list is paginated 25 at a time.

Behind the scenes: every submission is stored before any connector runs, so a slow or failing CRM never loses a lead; a failed connector marks the submission Failed and can be retried. Spam protection (honeypot, two-second minimum fill time, 20 submissions per 10 minutes per IP) is always on. Email needs SMTP_URL in the environment (for example smtp://user:pass@smtp.example.org:587?from=cms@example.org; smtps:// for port 465); without it the message goes to the server log and the delivery still counts as ok. Webhook and HubSpot URLs may not point at localhost or private networks unless you start the server with SPILLWAY_ALLOW_PRIVATE_URLS=1. If you build your own front end instead of the Form block, send the _t field (the time you rendered the form, in milliseconds) or submissions are rejected as too fast; see api.md.

File uploads: a file field stores what the visitor attaches under DATA_DIR/form-uploads/, outside the media library. The type is read from the file's bytes, not its name, so a renamed file is refused; the size and count limits are set per field. Spillway ships no virus scanner: scanUpload() in packages/core/src/forms/uploads.ts is the hook point, a no-op that an operator running ClamAV or a cloud scanner replaces (throwing from it refuses the file and the submission). Multipart submits may be as large as a media upload (25 MB); JSON and plain form posts stay at 256 KB.

CAPTCHA: Site settings → Forms picks a provider for every public form on the site: None (the honeypot, the two-second fill time and the rate limit), Cloudflare Turnstile or hCaptcha, each with a site key and a secret key. The secret is write-only: it is never shown again, and leaving the mask in place on a later save keeps it. With a provider configured the Form block renders its widget, the page's Content Security Policy allows the provider's script and frame, and the server verifies the token with the provider before storing a submission (the fill-time rule is replaced by the challenge); a missing or rejected token is refused. Custom front ends send the token as cf-turnstile-response, h-captcha-response or _captcha.

Reference. api.md, Forms; blocks.md, Form.

Redirects

What it is. Redirects keep visitors from landing on 404s when URLs change, and the Missed paths log shows what visitors asked for that did not exist.

Where. Sidebar → Marketing → Redirects, for managers and admins (site.settings).

Common tasks.

  1. Add a redirect. Add redirect opens a dialog: a from path (matched exactly, case-insensitively, with any trailing slash ignored), a target path or absolute URL, the status code (301 permanent or 302 temporary) and an enabled flag. The dialog refuses duplicates of an existing from path and a redirect that points at itself. Edit opens the same dialog; Delete asks for confirmation. The table lists every redirect with an Enabled checkbox you can flip in place and a hit counter that grows each time the redirect fires.
  2. Fix a missed path. The Missed paths panel on the right lists the paths visitors requested that returned 404, with the number of hits and when they were last seen. Create redirect opens the add dialog with that path already filled in; Dismiss drops it from the list. A path that already has a redirect shows a "Redirected" badge instead. Scanner noise such as .php, .env or wp-admin requests never reaches the list.
  3. Rename a page safely. Changing the slug of a published page creates a 301 from the old path automatically; it shows up in this table like any other. Drafts never create redirects, and moving a page back to an earlier path removes the stale redirect so visitors never loop.

Matching happens before the page lookup, ignores case and a trailing slash, keeps the query string (/old?utm_source=x lands on /new?utm_source=x) and counts a hit.

Reference. api.md, Redirects and the 404 log.

Experiments

What it is. A/B tests on any part of a page: an experiment holds variants a (the control), b and optionally c with weights and a goal (a form submission, a visit to a page path or a click inside a CSS selector); the Experiment block fills the slots; Results shows exposures, conversions and the rate per variant with the confidence of a two-proportion z-test.

Where. Sidebar → Marketing → Experiments, for managers and admins (site.settings).

Common tasks. Create the experiment first, then place an Experiment block in the page builder, enter the key and fill slots A, B and C; Start it (only a running experiment assigns variants; every other status shows A), Pause or Resume, and End when Results declares a winner at 95% confidence (ending is final). Assignment is sticky for 90 days through a functional cookie, and the page stays cacheable: it carries variant A and a small script swaps in each visitor's version.

Reference. personalization.md, Experiments; blocks.md, Experiment.

Audiences

What it is. Named rule sets that decide who sees the personalized version of a block: campaign parameters, query parameters, referrer, path, device, a time window or returning visitors, matched with all or any. A Personalize block shows its "Content for the audience" slot to visitors who match and its default to everyone else.

Where. Sidebar → Marketing → Audiences, for managers and admins (site.settings).

Common tasks. Define an audience (for example utm_campaign equals spring), place a Personalize block with that audience key, keep both slots the same shape so the swap does not shift the page, and preview the default content (draft previews render the default inline). Saving an audience evicts the pages that use it. Consent mode for the returning-visitor rule is under Site settings → Integrations → Privacy.

Reference. personalization.md, Audiences; blocks.md, Personalize.

Content types

What it is. The content model: each type is a list of typed fields, an optional public URL pattern and a title field. A type without a layout field is rendered by the server's article template (the title, the first media field as a hero image, then each field in order); a type with a layout field uses the page builder. You can add a layout field to any type.

Where. Sidebar → Structure → Content types, for managers and admins (types.manage). Every type you save appears in the Content group.

Common tasks.

  1. Create a type, for example a case study: New content type, then Basics: Key case_study (machine name; cannot be changed later), Label Case study, Plural label Case studies, Title field title, an optional description. Public URL: turn on "Has public URL" and set the Path pattern to /customers/{slug}. Tokens: {slug}, {id}, {type}, {yyyy}, {mm}.
  2. Add fields. Add field for each row. Each has a key, label, type, Required and Multiple checkboxes, and a details panel with help text, an AI hint, options (for select), the referenced type (for reference) and the vocabulary (for taxonomy). Suggested fields for the case study:
    KeyLabelTypeNotes
    titleTitletextrequired
    customerCustomertext
    industryIndustryselectoptions: Finance, Health, Retail
    summarySummarytextarea
    bodyBodyrichtextrequired
    logoLogomedia
    relatedRelated postsreferencerefType post, multiple

    Save. "Case studies" appears in the sidebar and you can create one straight away.

  3. Change or remove a type. Edit any field or the path pattern later (the key is fixed). A type that still has entities cannot be deleted; delete or move the entities first.

Reference. content-model.md; api.md, Content types.

Menus

What it is. The site navigation. Every site has a Main navigation menu, shown in the header, and a Footer menu, shown in the footer; both can be emptied but not deleted. Other menus can be created by key for blocks, modules and department sections.

Where. Sidebar → Structure → Menus, for managers and admins (site.settings); a section editor sees their section's menu under Structure.

Common tasks. The editor is a tree: Add item, Add sub-item (one level of nesting; the header shows parents as drop-down disclosures, the footer as a flat list), arrows to reorder, Delete, and per item a label (plus a label per extra language on a multilingual site), a link to a Page (search by title; the item then follows the page's URL and is hidden while the page is not published) or a URL (a path, https://, mailto: or tel:), and New tab. Save publishes the menu and clears the cached pages that show it. Kits can ship menus too: installing one fills the untouched starter menus.

The rendered navigation works without JavaScript and marks the current page with aria-current="page"; see theming.md for the markup and styling hooks.

Reference. api.md, Menus.

Brand

What it is. The site's look and voice in one place: logos and favicon, colors with their shades and dark and high-contrast variants, type, spacing, the named styles editors pick for blocks, the brand profile the AI follows, and a style guide built from all of them. Changes are saved as a draft, previewed on the real site and then published, so visitors never see a half-finished brand.

Where. Sidebar → Structure → Brand, for managers and admins (site.settings). The tabs are Identity, Color, Type, Space and shape, Styles, Voice and Style guide; the header carries the draft status, History, Import brand, Export, Preview, Discard draft and Publish. On a site that inherits another site's brand the header says so (Inherits <site>) and a locked tab's title ends in "(locked)".

Common tasks.

  1. Change the brand colors. On Color, pick or type the primary color (hex, rgb(), hsl() or a color name) and, if you use them, secondary, accent and neutral; each shows its ten generated shades. Text and background, the status colors (success, warning, danger), Dark mode (turn on Follow the visitor's dark mode setting and set the colors that replace the light ones) and High contrast colors sit below. The Contrast grid at the bottom checks text on background, links on background, and white and text on each brand color against WCAG 2.2, with a pass or fail badge for AA, AA large and AAA; fix any AA fail on a pair your pages use.
  2. Set the type. On Type, set the heading and body font stacks, the base size (16px is common) and the scale ratio; the sample shows the six sizes from small to heading 1.
  3. Space and shape. Corner radius, the space unit, the content max width and a shadow for raised surfaces; custom tokens (any --sw-<name> variable your own CSS or modules use) are listed below them.
  4. Shape the block styles. On Styles, pick a block on the left (Hero, Cards, Button, Call to action, Heading, Text and any module block with styles) to see its styles, each drawn as the real block in the draft's colors. New style (or Edit) opens a dialog with the name, the key pages store, a description, one choice per style slot (a Hero's background, text and layout, a card's border, fill and image position, a button's emphasis; only values built from the brand's tokens), Default for … blocks and Who may pick it (leave every role unchecked for everyone; managers and admins can always use every style). Duplicate copies a style, Make default makes it the one new and unstyled blocks use, Reset returns a built-in style you changed to how it shipped, and Delete removes a style you added (its blocks switch to the default). Built-in styles cannot be deleted. Publishing restyles every page that uses a changed style. See theming.md, Block styles.
  5. Add the logo and favicon. On Identity, choose the Logo (shown in the header instead of the site name), the Logo mark, the Logo on dark (for dark mode) and the Favicon from the media library. The social image stays under Site settings → SEO defaults.
  6. Preview and publish. Every change is saved to the draft a moment after you type (the header says when). Preview opens the home page with the draft in a new tab (only people who can edit the brand see it; visitors keep the published brand). Publish puts the draft live and clears the page cache (and the CDN); Discard draft drops it. History lists the last 20 published versions; Restore as draft brings one back to preview and publish.
  7. Import the brand you have. Import brand reads one of four sources: Design tokens (a Figma variables export, a W3C design-tokens file, a Tailwind theme or a stylesheet's custom properties, dark mode included), a Website (the home page's colors, fonts and logo, and with AI on a voice draft from its copy), a PDF brand guide (its palette, typefaces and voice, with optional logo files) or a Brand kit (another Spillway site's export: everything). Review the import shows each value next to the current one with a checkbox; Apply to draft writes only the checked rows into the draft (logos into the media library, voice rows into Voice). Nothing is published until you publish. Step by step: Import a design system from Figma; every source: theming.md.
  8. Write the brand voice. Voice is the brand profile every AI task reads: Who we are (organization name, boilerplate, audience), How we sound (voice, tone, examples of on-brand copy), Words (terminology, banned words and phrases). The banned words also feed the pre-publish checks. Draft from my site fills it in with AI from the site name and home page. The voice is part of the draft like the other tabs: the AI tasks and the checks use it once you publish.
  9. Share the style guide. Style guide shows the brand as one page, built from the live tokens: the logos on light and dark, the palette with its shades and the contrast grid, the type scale, spacing, radius and shadow, the voice rules, and every block in every style. With a draft, Side by side shows the draft next to the published brand and Draft only shows it alone. Duplicate style on any style opens the style dialog with a copy that Add style saves to the draft. Print or save as PDF prints the guide as the brand book. Publish the style guide at /brand (off until you turn it on and publish) puts the published guide on the public site at /brand with logo downloads, for agencies, vendors and departments; search engines are asked not to index it. See theming.md, Style guide.
  10. Export the brand. Export downloads the published brand as a brand kit (kit.json with "kind": "brand", the logos inside): name its Label, Key and Version and choose Download kit. Another Spillway site imports it with Import brand → Brand kit; it lands in that site's draft for review. See theming.md, Export the brand as a kit.
  11. Share a brand across sites. When other sites inherit this site's brand (set by an admin under Site settings → Sites), the tabs show Shared with N sites and a Locked for sites that inherit this brand checkbox per part (Identity, Color, Type, Space and shape, Styles, Voice); locks apply when you publish, and every inheriting site follows each publish. On an inheriting site each tab says whether its part is Inherited, Overridden (with Reset to inherited) or Locked (read-only, naming the site that locks it). See theming.md, Shared brands.

The tokens become CSS variables on :root of every public page (--sw-color-primary, --sw-color-primary-50 to -900, --sw-text-sm to --sw-text-3xl, --sw-space-unit and so on), and /api/theme/tokens.css serves the published set for other sites. Links to the old Theme and Brand tabs of Site settings open the matching Brand tab.

Reference. theming.md; api.md, Brand.

Kits

What it is. A kit is a bundle of content types, vocabularies, collections, forms, menus and starter pages, with or without sample content, installed in one step. Four kits ship with Spillway (higher education, city, intranet portal, nonprofit) plus the docs kit, the documentation as a site; more come from a package registry. The same screen exports the site's structure as JSON and imports one (config as code).

Where. Sidebar → Structure → Kits, for managers and admins (site.settings).

Common tasks.

  1. Install a kit. A brand kit installs nothing here: its values open in Brand → Import brand for review. Each card lists what the kit contains; Install creates the structure, Install with sample content also publishes the kit's example entries. The report lists every item as created, updated or skipped with the reason; installing twice never overwrites your changes. A kit that requires other kits, modules or a core version says so on its card; the confirm dialog resolves them and lists what will be installed first (dependency kits in order, modules from the registry) or why it cannot (a cycle, a kit nobody has, a trusted-tier module that needs the operator's opt-in), and Install stays disabled until the plan is clean. Step by step: Install a kit.
  2. Export the structure. Export structure downloads structure.json with the current content types, vocabularies with their term trees, collections and forms (webhook secrets are stripped); keep it in git.
  3. Import a structure from such a file: types, vocabularies, collections and forms whose key exists are updated in place, missing ones are created, and pages and sample content are ignored, so applying a structure never touches content.

Reference. kits.md, including the manifest format and dependencies; api.md, Kits and structure.

Import

What it is. The domain importer brings an existing website into Spillway without a database connection: it crawls the public pages, proposes content types, converts the bodies into blocks, extracts the theme and lands everything as drafts, with redirects, media and menus, inside a workstream. Drupal (JSON:API) and WordPress (REST) sites are read more precisely through their APIs when they answer. Nothing is published by the importer.

Where. Sidebar → Structure → Import, for admins (imports.manage). The list shows every job with its source, status, pages fetched and discovered, types, landed count and start time, and polls while a job runs.

Common tasks.

  1. Start a crawl. New import asks for the site's URL and the crawl options (maximum pages, delay between requests, robots.txt, sitemap, path filters, the Drupal and WordPress adapters, whether to keep redirects to other sites, and whether to land inside a workstream) and starts the crawl. Next to the start button, AI usage says how many AI calls and tokens the import's type proposals will use and how much of the monthly AI token cap is left; untick Use AI for type proposals to keep them heuristic for this job. When the estimate exceeds what is left, tick I understand: start anyway to start.
  2. Walk the five steps on the job screen: Crawl (progress, the events log and every page fetched with links to the source and the raw HTML), Types (the AI usage measured from the crawled pages, then a proposed content type per cluster of similar pages: approve it, map it to a type you already have, merge it into another cluster or skip it), Convert (fields and blocks for every page, a quality score and a Compare drawer with the original next to the result), Theme (the site's palette, fonts, logo, radius and content width with a confidence per token; edit and apply) and Land (the drafts, media, redirects and menus, then the report with a link to the workstream preview and the review queue).
  3. Review and publish. Preview the workstream, fix pages in the editor, then publish page by page from the review queue or merge the workstream to publish everything at once. The same runs from a terminal with spillway import <url>.

Step by step: Import a website.

Reference. importer.md for the crawl rules, the adapters, clustering, conversion, the theme heuristics and the CLI flags; api.md, Domain importer.

Registry

What it is. An instance started with REGISTRY_HOST=1 is a package registry for modules, kits and themes (this is how spillwaycms.com runs; see website.md). The sidebar then shows a Registry group (the admin asks GET /api/registry/status once per load; on any other instance the screens explain that the registry is off).

Where. Sidebar → Registry: Review queue, Packages, Publishers, Namespaces, Organizations and Advisories for admins (users.manage), and Publisher for every signed-in user with the registry.publish permission (which all roles carry).

Common tasks.

  1. Review a trusted module. Review queue lists the trusted-module versions waiting for a human before they are listed: name, version, publisher, declared capabilities, the automated checks and when it was submitted. A row opens a drawer with the README, the manifest, the Files the tarball holds (screenshots and the package documents open from the registry's files endpoint; the code comes with the tarball download) and every check result. Approve… lists the version, gives it the reviewed badge and puts it in search and the signed index; Reject… keeps it reachable by direct link with a warning. Both take a note for the publisher and are audited. With REGISTRY_FOUR_EYES=1 the queue notes that a version's own publisher and its namespace's maintainers may not approve it.
  2. Moderate packages. Packages shows everything published, unreviewed included, with kind, tier, latest version, downloads, status (reviewed, checks passed, unreviewed, deprecated) and three actions: Deprecate (with a note shown on the package page and at install time; Undeprecate reverses it), Yank… (pick a version and give a reason; it leaves the index and cannot be undone) and Advisory… (a security advisory with the affected semver range, a severity, a title and a link). Publishers lists who publishes with a Verified toggle that puts the verified badge on their packages; Namespaces lists the @scope/ prefixes with their owner and maintainers and a Transfer… dialog for disputes; Advisories lists every advisory with Add advisory and Withdraw.
  3. Publish your own package. Publisher is your corner of the registry: create or edit your publisher profile (handle, display name, website; the handle is also created on your first publish), see your packages with each version's checks and review status (and yank your own), manage your Organizations (below), and follow How to publish: Create publish token mints an API token with only registry.publish, then spillway publish . --registry <this site> --token spw_… from a folder with a manifest.json (module) or kit.json (kit; a theme adds "kind": "theme" and screenshots). Sandboxed extensions, kits and themes are listed as soon as the automated checks pass; trusted modules wait for the review queue.
  1. Publish as a team. Under Organizations on the Publisher screen, Create organization makes a shared handle you own (handles share one space with publisher handles). Open an organization to add members by publisher handle with a role (owner: everything; admin: publishes under the organization's namespaces and manages maintainers; member: listed, publishes only as a maintainer), change roles, remove members, move one of your own namespaces into it, set each namespace's maintainers, or leave. An organization always keeps one owner. Package pages then say "by <organization>" and link its public page at /registry/orgs/<handle>. Admins verify organizations on Registry → Organizations (the verified badge then shows on every package under its namespaces), see how many namespaces each owns, and can move any namespace into an organization from the Namespaces screen's transfer dialog by entering the organization id. Leaving an organization takes nothing back: what a member published stays theirs. See registry.md, Organizations.

The public side (browse, search, package pages with the install command, capability sheet, versions, advisories, README and the agent manifest; the theme gallery; publisher and organization profiles) is built from the registry blocks on ordinary pages: /registry holds a RegistryBrowse block and /registry/any a RegistryPackage block, the catch-all page that answers every /registry/<name>; /registry/publishers/any holds a PublisherProfile block, /registry/orgs/any an OrgProfile block and /themes the ThemeGallery. Create them in the admin like any page or ship them in a kit; spillwaycms.com's come from its website kit (website.md). To install a kit or theme that a registry publishes, System → Modules → Install from registry covers modules; for kits and themes use spillway kit install <name> --registry <url> or POST /api/kits/install-from-registry { "name" } (users.manage; the sha256 is verified against the signed index).

Reference. registry.md, including installing from a registry; blocks.md, RegistryBrowse; the agent guide Publish a package.

Site settings

What it is. Everything that configures the site rather than its content, in tabs: Site, Languages, Sections, Sites, AI, Notifications, Integrations, Forms, Media, Cache, Sign-in and System. Each tab that edits settings has a sticky save bar; saving clears the page cache (and purges the CDN when one is connected) so changes are visible at once. Logos, colors, fonts and the brand voice moved to Brand; the Site tab links there.

Where. Sidebar → System → Site settings, for managers and admins (site.settings); the Sites and Sign-in tabs need an admin, and System needs users.manage. On a multisite instance the settings belong to the site chosen in the switcher; content types, vocabularies, shared blocks, users, API tokens, sign-in providers, notification rules, modules and the AI settings are shared by the whole instance.

Site: general, SEO defaults and content

  • General: Site name, Tagline, Base URL (public origin, used for canonical links), Default language (managed under the Languages tab together with the other locales), Home page (the published page shown at /; empty shows a default welcome page).
  • SEO defaults: Title suffix (appended to every page title, for example | Acme; the wizard sets | <site name>), Default description, Default social image, robots.txt (a text box for what /robots.txt serves; leave it empty for the default, which allows crawlers everywhere except /admin, /api/, /_preview/ and /search and points at /sitemap.xml; Reset to default clears a custom value) and Publish /llms.txt (a plain-text summary for AI crawlers: site name, tagline, description and the published pages with their paths and summaries).
  • Content: Revisions to keep per item (default 50; see Revisions).

What the public site serves from these: /sitemap.xml with every published page, its path and last-modified date (a sitemap index with one file per language on a multilingual site); /robots.txt pointing at it; JSON-LD structured data in every page's head (WebSite and Organization on the home page, WebPage for pages, Article with publish date, author and hero image for posts and other types that have a publishDate or body field, and a BreadcrumbList for nested paths); and site search at /search?q=, which indexes the title, the text, textarea and rich text fields, the summary and the words inside layout blocks of every page and post as it is saved, shows published content only to visitors (editors who are signed in also find drafts), highlights the matching words and matches the last word as a prefix (kaya finds "kayak"). Set BASE_URL (or the site's base URL) so the sitemap, feeds and canonicals carry absolute links. POST /api/search/reindex or spillway search reindex rebuilds the index, for example after importing content. See api.md, SEO files and Search.

Languages

The list of locales as BCP 47 tags (en, es, pt-BR): Add language (the tag is validated as you type and canonicalized on save, so pt-br becomes pt-BR), choose which is the default and whether the default is prefixed in URLs (/en/about instead of /about). Removing a language that still holds content is refused: a dialog lists the counts per language and offers Remove anyway, after which that content stays stored but is no longer reachable on the public site. The Translation management system card on the same tab is described under Translations. See localization.md.

Sections

Department delegation on one site: a section names the part of a site a department owns with a label, a key, a path prefix such as /engineering and optionally the menu its editors may change (New section). Entities under the prefix join the section automatically, at once and on every save. A section editor who signs in sees only their section's content in lists, the review queue and the editor, creates pages under its prefix, publishes them, edits the section's menu (listed under Structure) and sees no site settings, content types, forms, redirects or users. Roles per section are given under Users. Step by step: Add a department section. See multisite.md.

Sites

One instance can host several sites (admins only). The tab lists every site with its key, domains, status and languages. New site asks for a name, a key (fixed once created), the domains with one marked primary, the languages, the Brand (its own, or Inherit the brand of <site>: the shared brand of another site, which locks some parts; the Brand column of the table shows it) and the status; Pause shows visitors a 503 page while the admin and the API keep working, Resume brings it back, and Delete asks for confirmation that spells out that all of the site's content is deleted (pages, media, menus, redirects, collections, forms and submissions, alerts, experiments and analytics). The default site cannot be deleted; its Delete button says why.

When you can open more than one site, the site switcher in the top bar lists them with your roles on each and marks the current one. Choosing a site is remembered in this browser, every API call carries it, your permissions are re-read for that site and every screen reloads with that site's content, media, menus, forms and settings; View site and previews open that site too. With a single site the name is shown without a menu. See multisite.md.

AI

The brand profile every AI task is told (the voice) is edited under Brand → Voice.

AI turns the AI layer on and configures it: General (enabled, monthly token cap), Providers (Anthropic, OpenAI, Google Gemini, xAI and an OpenAI-compatible endpoint, each with an API key that is encrypted at rest and never shown again, a model and, where needed, a base URL that must be a public https:// host), Tasks (which provider and model handle Page from brief, Field copy, Summaries, Translate, Alt text, Content type design and Site import) and Usage this month (calls and tokens by task and by user, and the share of the cap used). Keys can also come from the environment (ANTHROPIC_API_KEY and friends). Nothing the AI produces is published or saved by itself. See api.md, AI and the guide Build a page with AI.

Notifications

The rules that turn editorial events into messages. A rule is an event, a channel and optionally a list of content types it applies to:

  • Events: submitted for review, returned to draft, published, unpublished, deleted, comment added, scheduled publish failed, form submitted, workstream merged, translation ready, backup failed (a scheduled or manual backup run of the instance failed; see System).
  • Channels: Email to the item's author, to everyone who can publish, or to a fixed list of addresses; Slack through an incoming webhook URL; Webhook (a signed JSON POST, the same format as form webhooks, with X-Spillway-Event naming the event; this is also how a headless front end learns about publishes, see adapters.md).

Email needs SMTP_URL on the server (see install.md); without it, messages are written to the log and the outbox marks them as delivered with a note. Author and reviewer mail honors each person's own switches under the account menu → My preferences (review activity, comments), and nobody is told about their own action. Test send pushes a sample event through a rule straight away; for author and reviewer rules the test goes to you.

Messages are queued in an outbox and sent by the same ticker as the scheduler. A failed delivery is retried after 30 seconds and again after two minutes; after three attempts it is marked failed. The Outbox table on the same tab shows the last 500 deliveries with their status, attempts and last error. See api.md, Editorial.

Integrations

The Google Tag Manager container id, a GA4 measurement id (used when there is no GTM container), a Plausible domain, custom <head> markup (only script, meta and link tags are kept) and the hosts the Embed block may frame. The server injects the tags into every public page, with the GTM noscript iframe after the body tag. Privacy on the same tab has one switch, consent mode: off, or a consent banner (Accept / Decline) after which Google tags start with storage denied and the returning-visitor cookie is set only on accept. The built-in Analytics counter sets no cookies and is unaffected. See personalization.md, Consent mode.

Forms

The CAPTCHA provider for every public form: None, Cloudflare Turnstile or hCaptcha with a site key and a write-only secret. Described under Forms.

Media

The DAM connector (Cloudinary, Bynder or Brandfolder), its default folder, Test connection and the Webhook URL to register. Described under Media.

Cache

The CDN in front of the site and the cache headers. Provider (Cloudflare, Fastly, Bunny or a generic signed webhook) with its write-only credentials and the zone, service or pull zone id; Enterprise zone: purge by Cache-Tag for Cloudflare; Purge everything when the theme or the site settings change; Test connection. Once connected, every publish, unpublish, delete, menu, shared block, media or theme change that evicts the page cache also purges the CDN: collected for two seconds, sent in the background, retried on failure. Cache headers sets s-maxage and stale-while-revalidate for public pages (defaults 60 and 300 seconds; raise them when a purge provider is connected). Purge sends a purge by hand: tags such as menu:main, paths such as /about, full URLs, or Purge everything. Last purges lists the site's recent purges with their trigger, what was purged, status, attempts and the last error. spillway cdn purge [--all] does the same from the terminal. See caching.md.

Sign-in

Enterprise sign-in providers (admins): OpenID Connect, SAML 2.0 and GitHub, each with a slug, just-in-time account creation, default roles, a group claim mapped to roles and an allowed email domain list; Add provider, Test, enable or disable. Each enabled provider becomes a Continue with … button on /admin/login. The Provisioning (SCIM 2.0) panel on a provider creates, rotates and revokes the bearer token an identity provider uses to create, update and deactivate accounts and push groups, and shows the last sync and the last 20 SCIM events. Step by step: Set up SAML with Okta. See sso.md, including SCIM.

System

Instance-wide operations, for people who can manage users. The Backups card shows how backups are configured on this instance: the target (an s3://bucket/prefix or a directory, never a credential), the schedule and when it next runs, the retention and whether backups are encrypted. Every backup carries the instance's key files (which decrypt the stored SSO, AI, DAM, TMS and CDN credentials), installed modules and form uploads along with the database and media; without BACKUP_ENCRYPTION_KEY the card shows a warning with how to set it, and runs that hold the keys in clear are marked Keys in clear. Backups are configured with environment variables (BACKUP_TARGET, BACKUP_SCHEDULE, BACKUP_RETENTION_DAYS, BACKUP_ENCRYPTION_KEY), not here; until BACKUP_TARGET is set the card says how. Run now starts a backup at once and reports its size; the table lists the last 200 runs with when they started, the trigger (schedule, by hand or CLI), the status, the size, where the run landed, how many media files were new and the error of a failed run. A failed run also sends the Backup failed notification to any rule for it (see Notifications). Restoring is done from the terminal with spillway restore on a stopped server. The Status page card links the instance's public /status and /status.json. See install.md, Backups and Status.

Reference. api.md, Site, Sites, Sections, System, Enterprise sign-in and caching.md.

Users

What it is. The people who can sign in, their global roles, and on a multisite instance their roles per site and per section.

Where. Sidebar → System → Users (admins, users.manage). The list shows each user's sign-in method and an Access column (how many sites and sections).

Common tasks.

  1. Invite a user. Invite a user asks for name, email, password and roles (default author). Users do not receive an email; give them the password you set. Accounts created by SSO or SCIM have no password until an admin sets one.
  2. Edit a user. Name, roles and password (leave the password empty to keep it); email cannot be changed. Changing a password or the roles ends the user's other sessions. You cannot remove your own admin role or delete your own account.
  3. Grant access per site or section. The user screen has three panels: the profile with the Global roles (which may be empty), Sites with a role checkbox matrix per site, and Sections with the same per section, followed by an Effective access summary that spells out what the person can do on each site. See multisite.md.

Reference. api.md, Users; reference/permissions.md.

Modules

What it is. Extensions: trusted modules (JavaScript in the server process, which can add blocks, field types, admin pages, providers and AI tools) and sandboxed extensions (WebAssembly with declared capabilities, for hooks, transforms, webhooks and AI tools). Both come from a package registry or an uploaded archive and are recorded with their manifest, enabled state, settings and last error.

Where. Sidebar → System → Modules, for managers and admins (site.settings). Installing, enabling, disabling or removing a trusted module needs an admin, and installing one from the web needs the server to run with SPILLWAY_TRUSTED_MODULE_INSTALL=1.

Common tasks. Install from registry (search the registry the instance is pointed at, then Install) or Upload a module archive (.tgz); toggle enable and disable (a trusted module schedules a graceful restart and the screen shows a Restarting banner); edit a module's Settings (JSON); open its log drawer. In safe mode (--safe-mode) modules are listed with a banner and cannot be enabled until a normal restart.

Reference. modules.md, including the trust model; registry.md for where modules come from; the agent guide Build a module.

Account menu

API tokens (/admin/tokens): create a token with a label, an expiry and a subset of your permissions (the Drafts only preset is what an agent needs), copy it once, revoke it any time; admins see everyone's tokens. My preferences: whether you get email about review activity and comments. Interface language: the admin's own language, saved on your account. The theme switch (light, dark or system), Keyboard shortcuts and Log out sit in the same menu. Tokens are how scripts, agents and the MCP server act on your behalf; see mcp.md and agents/connect.md.

Next steps

Spillway 0.1.0 · Edit this page on GitHub

Was this page helpful?