Plugins

The heavyweights

Bigger capabilities for malleable files. Some are configurator checkboxes, some are their own libraries; each earns its place by solving a whole class of problem.

cms checkbox plugin

“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/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.

quickcrop plugin · loads with cms, or ask by name

“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, so cropping chrome never reaches the saved file.

richclay checkbox plugin · on by default

“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.

undo checkbox plugin

“⌘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/clay.js?plugins=undo"></script>
upload plugin · ask by name

“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/clay.js?plugins=upload"></script>
wire plugin · ask by name

“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. That split is the whole design, and it is why the wire has no opinion about what a payload contains.

<script src="https://clayjs.com/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.

sap separate library

“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/sap.js"></script>
data separate library

“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/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().