The heavyweights
Bigger capabilities for malleable files. Some are configurator checkboxes, some are their own libraries.
“Other people need to edit this page's content without touching its code.”
A content sidebar that builds itself from your page. You name the parts of the file that count as content, and the CMS turns them into friendly fields: text, rich text, images, lists. Whoever you share edit access with sees the panel, not the markup.
Setup is two tags. Check “A content sidebar” in the configurator (or add ?plugins=cms yourself), then tell it what's content with a rules tag:
<script src="https://clayjs.com/v1/clay.js?plugins=cms"></script>
<script data-rules-name="cms" data-rules-version="1" type="application/json">
{
"title": ".page-title",
"intro": "p.intro",
"avatar": "img.avatar@src",
"tags": "ul.tags li[]",
"products": [".product", { "name": ".name", "price": ".price" }]
}
</script>
Each entry is a field: a name, then the selector it edits. @src becomes an image-upload control, li[] a reorderable list, and the products shape a list of cards. Open the sidebar with ?cms=true on the URL or clay.cms.open() from a button. The same rules tag can carry more tokens (data-rules-name="api cms") to also power the data plugin's JSON API, so you configure content once.
“People upload a 4000px photo and it lands in the page sideways and uncropped.”
A crop modal for image uploads: pick a file, drag a box, confirm. It resolves a cropped, re-encoded blob and a data URL. The CMS uses it for any image field marked data-hcms-crop, so ?plugins=cms loads it for you; ask for it by name to call it from your own code.
<img class="avatar" data-hcms-crop="1:1" src="avatar.jpg">
const shot = await clay.quickcrop(file, { aspect: 16/9 });
// { blob, dataURL, width, height }, or null if they cancelled
Aspects are 1:1, 16:9 or free on the attribute, a plain number in code. The modal and its stylesheet are marked save-remove, the legacy alias for no-save, so cropping chrome never reaches the saved file.
“Editable text should mean bold, lists, and links, not just characters.”
The editable attribute from the tutorial. A floating toolbar appears while editing; output stays clean HTML. It's part of the default clay.js because editing text is the heart of a malleable file; uncheck it in the configurator if you're building something else.
<article editable>Multi-line rich text…</article>
<h1 editable="single-line">Page title</h1>
<p editable="single-line no-toolbar">Caption</p>
Tokens combine like classes: single-line, no-toolbar, toolbar-on-select. In the saved file the attribute is an inert marker; no editor chrome is ever written to disk.
“⌘Z should undo what I did to the page, not just the last keystroke.”
Document-wide undo/redo built on DOM mutations: moves, deletions, and attribute changes all reverse cleanly. Respects clay="no-undo" regions.
<script src="https://clayjs.com/v1/clay.js?plugins=undo"></script>
“Someone needs to put a file into this page without knowing what a data URL is.”
A file picker that gets the chosen file into the page. Pair it with the CMS for image fields, or call it yourself from a button.
<script src="https://clayjs.com/v1/clay.js?plugins=upload"></script>
“I want a program on my own machine to work on this file while I have it open.”
A per-file control channel between the page and a process running in your terminal. The page sends a request, the process answers with progress, and the process edits the file. HTML never travels over the wire: the change reaches your open page as an ordinary external file change, through live sync. Nothing in the wire depends on what a payload contains.
<script src="https://clayjs.com/v1/clay.js?plugins=wire,sync"></script>
const run = clay.wire.send({ ask: "tighten the intro" });
clay.wire.on((state, frame) => console.log(state.state, frame && frame.text));
const outcome = await run.done;
send returns its handle immediately, and handle.done resolves with the final snapshot and never rejects. on takes a function, not an event name. Its second argument is the inbound frame when one caused the update, which today means a status line and nothing else: state.text is sticky across later states, so the frame is the only way to tell a new line from the same line arriving again. Also cancel, get, list and isBusy. It runs in view mode too, so a read-only review page can drive one; nothing in it assumes the save lane exists. The stream is lazy and idle between requests, because a browser allows only six connections per origin and live sync already holds one.
“I want reactive templates, with the DOM itself as the state.”
Reactivity without a virtual DOM or a store: attributes and elements are the source of truth, and templates re-render when the DOM they depend on changes. Made for malleable files, where the page already is the database.
<script src="https://clayjs.com/v1/sap.js"></script>
“I want the page's content in and out as JSON.”
clay.extractData() reads your page into structured JSON; clay.applyData(data) writes JSON back into the page. The same mapping powers a read-only /_/api endpoint on platforms that support it, so a malleable file can double as an API.
<script src="https://clayjs.com/v1/clay-data.js"></script>
Both attach to window.clay. From an inline script, wait for the tag first: await clay.loaded.data, then const data = clay.extractData().