- ClayJS
- Reference
- Plugins
- CMS
CMS
A content sidebar built from your own page: rules, field controls, views and the JavaScript API.
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.
Let’s start with a personal page: your name, a photo, and a link. All you have to do to enable the CMS is a short description of which parts are content. Then Clay.js CMS gives you controls for editing them. You can type directly on the page or use a sidebar. Both change the same HTML.
This guide uses HTML Clay to save a file on your computer. You need the HTML Clay app, a text editor, an internet connection to load ClayJS (or download the standalone version), and a photo to upload to the page. The CMS library is free to use; you do not need a Hyperclay hosting account.
(For rule syntax, JavaScript methods, and custom controls, see the CMS reference.)
Create the page#
Install HTML Clay and launch the app.
Create an example folder inside your home folder, such as Documents/personal-cms. Put a JPEG photo in the example folder and name it portrait.jpg. In your text editor, create profile.htmlclay beside it with this HTML:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My profile</title>
<style>
body { max-width: 32rem; margin: 3rem auto; padding: 0 1.5rem;
font: 1.2rem/1.5 system-ui; color: #20352b; background: #f5f4ed; }
img { width: 8rem; height: 8rem; object-fit: cover; border-radius: 50%; }
h1 { line-height: 1.1; }
a { color: inherit; }
</style>
</head>
<body>
<img class="photo" src="portrait.jpg" alt="My portrait" data-hcms-crop="1:1">
<h1 class="name">Alex Rivera</h1>
<p class="bio">Making small things for the web.</p>
<a class="website" href="https://example.com">My website</a>
<script data-rules-name="cms" data-rules-version="1" data-cms-view="inline" type="application/json">
{
"name": ".name",
"bio": ".bio",
"photo": ".photo@src",
"website": ".website@href"
}
</script>
<script src="https://clayjs.com/v1/clay.js?plugins=cms,indicator"></script>
</body>
</html>
The rules tag maps labels to elements. "name": ".name" means the name field edits the element with class name. @src edits the image source, and @href edits the link destination. The CMS uses the content already in those elements. data-cms-view="inline" tells the page to open the editor on the page instead of in the sidebar.
Save it as plain text with the exact filename profile.htmlclay. Double-click the file to open it through HTML Clay. If the app asks about the example folder you created, choose Trust this folder.
Edit on the page#
Find the floating Edit content control at the bottom right of the page and click it. If it is missing, see troubleshooting. Click the name or bio and type. To edit the website address, click the pencil beside the link and change the value in the popover. The visible link label stays “My website” because this example maps only its destination.
For a photo, click its edit handle and choose an image from your computer. You can reuse portrait.jpg to practice. The data-hcms-crop="1:1" attribute opens a square crop window. Confirm the crop. Keep the editor open until the progress bar disappears and the selected photo appears on the page, with no error below the upload control. A preview inside that control alone does not mean the upload has finished.
Without data-cms-view="inline" (or with data-cms-view="sidebar"), Edit content opens the sidebar instead and presents the same fields together in a side panel.
Save and check the file#
Check that the page shows the text, link address, and photo you intended to change. Press Cmd+S on macOS or Ctrl+S on Windows or Linux. Wait for the small Saved status chip at the bottom right. If it says Couldn’t save or Offline, not saved, keep the tab open and resolve the error before trying again. Do not reload while a save is failing. Closing the editor alone does not save.
Reload the page. Each change you made should remain. Reopen profile.htmlclay in your text editor to read the saved file, discarding any stale editor buffer without saving it. Find the text you changed. It is now part of the HTML, alongside the rules and the page’s styles.
The editing popovers and sidebar are temporary controls. They are excluded from the saved page. Uploaded photos go into the host’s uploads folder (~/htmlclay/uploads/assets-profile/ on HTML Clay), and the HTML links them as /_/uploads/assets-profile/..., which keeps working when the page is renamed or moved. To take the page somewhere else with its photos, use Export as zip.
To enable autosave, first save any browser edits and wait for Saved. Close the browser tab and reopen the latest file in your text editor. Change its opening tag to <html lang="en" autosave>, save, then open the file through HTML Clay again. Subsequent edits save after a short pause. HTML Clay must still be running; autosave does not replace the save host.
Use your own page#
For the same local workflow, make a copy of your page with the .htmlclay extension in a folder inside your home folder. Keep its local images, stylesheets, and other assets at the relative paths the page uses. Add the ClayJS script and a rules tag that points at the content you want to edit. Open the copy through HTML Clay and trust its folder when asked. If the page already loads ClayJS, add cms to that script’s plugin list rather than adding a second loader.
Start with a few fields and check each one. A rule for a heading should point at the heading, not a wrapper containing the whole page. For repeating links, cards, or gallery items, use a list rule from the reference.
An AI coding agent can help write the rules. The reference includes a configuration prompt. Review the result and try the fields before handing the page to someone else.
If something does not work#
- No Edit content button: confirm the filename ends in
.htmlclayand open it through HTML Clay from your file manager. Then check the rules tag and ClayJS script. Opening the file directly asfile://does not provide the saving connection. - A linked page opens read-only: open its
.htmlclayfile from your file manager. To let linked.htmlclaypages in this example folder open editable too, use Trusted Folders in HTML Clay’s menu and choose Trust a Folder. Copy any file outside your home folder inside it, along with its assets, before opening it through the app. - The page says the app is out of date: update HTML Clay to 1.9.0 or newer, then reopen the file through the app. Changing the rules or forcing edit mode does not repair an older host’s saving connection.
- Edit content opens the sidebar instead of editing on the page: check that the rules tag has
data-cms-view="inline". Without that attribute, the page opens the sidebar. - A field is missing or points at the wrong content: check its selector against the actual HTML. The sidebar can also expose fields that have no visible target on the page.
- The photo does not load: confirm
portrait.jpgsits besideprofile.htmlclay. After an upload, keep the generated asset folder beside the page too. - An upload fails: resolve the error shown below the upload control, then choose the file again.
- A save fails: keep the page open with your edits intact. Confirm HTML Clay is still running, then try Cmd+S or Ctrl+S again. Follow any permission or conflict message shown by the host; reloading is not a fix for an unsaved change.
The reference#
Hyperclay CMS creates editing controls from rules that describe content in an HTML page. The page remains the content store. The CMS reads values from its elements and writes edits back to them.
For a complete first example, use the CMS guide. This page explains the model, rule syntax, controls, and JavaScript API.
Content: Rules, Rich text, Lists, Views, Field controls, Page style, Templates, Images.
Integration: Load the CMS, JavaScript API, Open options, Events, Saving, Coding-agent prompt, Hosted AI.
A content sidebar#
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.
The page, the rules, and the editor#
A rules tag gives names to the content you want to edit:
<h1 class="name">Alex Rivera</h1>
<p class="bio">Making small things for the web.</p>
<script data-rules-name="cms" data-rules-version="1" type="application/json">
{
"name": ".name",
"bio": ".bio"
}
</script>
The CMS reads the named elements into a temporary editing model and builds controls for those fields. Changing a control writes to the corresponding page element.
There is no separate CMS database to synchronize. The CMS controls content editing; HTML Clay or Hyperclay supplies saving and uploaded-file storage. This page covers the CMS API, not building a custom save host.
Load the CMS#
<script src="https://clayjs.com/v1/clay.js?plugins=cms"></script>
Use a classic script tag for the loader, without type="module". ClayJS exposes the CMS as clay.cms. Put initialization after that loader and wait for clay.ready before calling the CMS:
clay.ready.then(() => {
clay.cms.open({ view: 'inline' });
});
The script and its imported modules need a network connection unless you serve a complete local copy. Copying only clay.js does not include those modules. Packaging a self-hosted ClayJS distribution is outside this CMS reference.
Edit mode and dependencies#
On a page in edit mode with a cms rules tag, the floating Edit content control opens the page’s declared view. HTML Clay 1.9.0 or newer supplies a savetoken on the root of a registered document’s served HTML. Opening a file inside your home folder through the app registers it; durable folder trust is not required for that file. Files outside your home folder cannot be opened through HTML Clay. ClayJS recognizes the token as edit mode and loads the editing and saving modules. Older HTML Clay versions use a token this ClayJS build no longer accepts; update the app rather than forcing edit mode.
The CMS and crop library also load outside edit mode, but RichClay, the host uploader, and saving do not. If you open a CMS session there, text elements use popovers instead of in-place RichClay editing, and image or file picks can embed data URLs. Native inputs mapped through @value remain native controls. See Saving and ownership for the permission boundary.
Build compatibility#
This reference targets the two-view HyperCMS 0.11.0 build. After readiness, typeof clay.cms.currentView === 'function' checks for its view API. An older sidebar-only bundle lacks that method. Waiting for clay.ready does not upgrade the bundle. The API table describes the method’s return values.
Rules#
Use type="application/json" and data-rules-version="1". The default rule source is the cms token in data-rules-name. A tag can carry multiple space-separated tokens, such as data-rules-name="cms api".
Opening a new session throws if no tag matches the requested token or its rules version is unsupported. Correct the tag before calling open() again.
| Rule value | Content it describes |
|---|---|
".name" | Element text |
"a.website@href" | A link destination |
"img.photo@src" | An image source |
".price@data-cents" | An HTML attribute |
"input.agree@checked" | A checkbox’s checked state |
"input.name@value" | A native input’s current value |
".bio@innerHTML" | Markup inside an element |
".tag[]" | A list of scalar values |
[".card", { "title": ".title" }] | A list of objects, with selectors scoped to each card |
{ "name": ".name", "bio": ".bio" } | A group of fields |
Map a text field to the element holding that text. Mapping a large wrapper as one text field can replace its child structure when you edit it. Prefer a separate rule for each piece of content.
Use unique leaf keys across the rules tree. In this build, repeating a key in different groups, such as title and metadata.title, can bind both fields to the first generated control and overwrite both targets. Use distinct keys such as pageTitle and metadataTitle.
Rich text#
An explicit @innerHTML rule preserves markup within the selected element. Use it when retaining formatting is part of the field’s contract.
With richText enabled, the default, a bare scalar selector is also upgraded to @innerHTML when its first matching target contains child elements. An explicit @property or scalar-list rule ending in [] is never automatically upgraded.
The sidebar and inline views differ inside object lists. The sidebar does not automatically upgrade those rules. Inline mode does: if any row’s matching target contains markup, that child rule is upgraded for every row. For consistent behavior in both views, write @innerHTML explicitly on those fields.
The inline toolbar offers formatting only when the field’s rule can preserve it. The richText option disables automatic upgrading when set to false; it does not turn an explicit @innerHTML rule into plain text.
Lists#
An object list uses [itemSelector, shape]. Each matching element is one item, and the shape’s selectors run within that item:
<style>
[cms-template] { display: none; }
</style>
<ul class="links">
<li class="link">
<a href="https://example.com">My projects</a>
</li>
<li class="link" cms-template>
<a href="https://example.com">New link</a>
</li>
</ul>
<script data-rules-name="cms" data-rules-version="1" type="application/json">
{
"links": [".link", { "label": "a", "url": "a@href" }]
}
</script>
The editor supplies add, remove, and reorder controls. New items copy the first existing item’s structure. The cms-template item is excluded from content data and supplies the structure when the list is empty. It belongs inside the list container.
In this example, a new link starts with an empty label and URL. The seed supplies structure, not default field values.
Hide the seed with [cms-template] CSS, as above, not a hidden attribute or inline display: none. Cloning removes the cms-template marker, so the new content item becomes visible. A copied hidden attribute would keep it invisible.
Without an existing item or a matching seed, adding an item fails with EmptyListInsert and leaves the page unchanged. Add a seed inside the list container before retrying.
Inline and sidebar views#
Both views use the same rules and editing session. A page declares its view on the rules tag:
<script data-rules-name="cms" data-rules-version="1" data-cms-view="inline" type="application/json">
inline edits text where it sits and opens small popovers for everything else. sidebar, and also no attribute at all, opens the form in a side panel. An unknown value opens the sidebar and logs a console warning.
| View | Interaction |
|---|---|
| Inline | Click text to edit it in place. Handles beside images, links, and other fields open their controls in popovers. Visible lists have nearby add, remove, and move controls. |
| Sidebar | Edit the fields together in a generated form beside the page. It can expose fields without a visible target in the page layout. |
In inline mode, writable inputs, textareas, and selects mapped through @value keep their native controls. Text elements use RichClay, the text editor loaded by ClayJS. Images and attribute fields use popovers. Text also falls back to a popover if RichClay is unavailable or cannot attach to the target, such as a table element or a non-HTML element.
The floating bar’s editable areas number counts field handles, not all editable content. It does not count text edited in place or native inputs. Resolved fields whose elements are hidden or too small to target get a separate message beside Edit in the sidebar; choose that action to reach them in the form. A rule that matches no element is unresolved, not part of that message’s count; correct its selector or add the missing content.
Hide controls hides the inline list controls. It does not close the editing session. Close editor ends the session and keeps edits in the current DOM. Saving is a separate action.
The floating Edit content control opens and closes whichever view the page declares. It does not switch views and does not remember a view per origin. A bare open() uses the open session’s view if one is open, and otherwise the declared view. Use clay.cms.open({ view: 'inline' }) or open({ view: 'sidebar' }) to override the declaration for one session.
The URL parameter ?cms=true opens the default editor; it does not specifically select inline mode or grant saving permission. Outside edit mode, the dependency limits still apply.
Refresh behavior#
The CMS subscribes to ClayJS’s document-wide mutation observer, then rereads content within pageRoot, document.body by default, after 100 ms without another qualifying change. The observer ignores library controls and excluded regions such as no-watch. Continuous changes can postpone the automatic refresh; call clay.cms.refresh() when your integration needs an explicit refresh.
Normal refreshes preserve the focused form field’s in-progress value. Undo and redo deliberately replace that value with the reverted content.
Field controls#
The rule and the page element determine the default control. @src selects an image upload control, @checked selects a checkbox, @innerHTML selects a rich text control, a rule ending in @--color or @--color-<name> selects the color control, and a rule ending in @--font or @--font-<name> selects the font control. Set data-hcms-component on a target element to request a specific control.
| Component | Configuration |
|---|---|
textarea | A multiline text field |
richtext | A rich text control; use @innerHTML to retain its markup |
number | A numeric input; getData() returns "5", not 5, for a value of five |
checkbox or toggle | A boolean field |
select or radio | Add space-separated values such as data-hcms-options="low medium high"; use a custom template for values containing spaces |
image or file | An upload field |
color | A color control for a CSS custom property; inferred for @--color and @--color-<name>, and narrowed with data-hcms-options |
font | A font control of 40 Google Fonts and system stacks; inferred for @--font and @--font-<name>, and narrowed with data-hcms-options |
variant | A row of layout icon tiles configured with data-hcms-options tokens such as "1:cols-1 2:cols-2 3:cols-3" |
chips | On a scalar list’s container, a compact editable list |
A field-specific <template data-hcms-tpl="path"> overrides the default control.
Within a repeating rule, control attributes are sampled from the first matching element, so configure corresponding elements consistently.
If a numeric field contains a value such as $5, it falls back to a plain text control so opening the editor does not erase that value. A checkbox or toggle on a rule other than @checked accepts true, false, or an empty value; other values fall back the same way. An accepted empty value reads as unchecked; the next commit writes false back to the mapped content, even if another field caused that commit.
Page style: colors, fonts and layouts#
These controls change how a page looks rather than what it says. They write CSS custom properties or attributes, and the page’s own CSS decides what those do.
@--name rules. A rule ending in @--name reads and writes one CSS custom property in an element’s style attribute and leaves every other declaration alone:
<html style="--color-accent: #0284c7; --font-body: 'Inter', ui-sans-serif, system-ui, sans-serif">
<style>
a, .button { color: var(--color-accent); }
body { font-family: var(--font-body); }
</style>
{ "theme": { "accent": "html@--color-accent", "body": "html@--font-body" } }
Names are case sensitive. Clearing a field removes the property, so the page falls back to its own CSS.
Color (inferred for @--color and @--color-*): a swatch grid of the full Tailwind palette (26 hues by 11 shades) plus a custom color picker. data-hcms-options narrows it: color ids (sky-600), whole hues (sky), whole shades (500), plain CSS colors, and custom to keep the custom picker. A narrowed list hides the custom picker unless it includes custom. A clear button empties the property.
Font (inferred for @--font and @--font-*): a list of 40 Google Fonts, each shown in its own face, plus the system stacks. data-hcms-options narrows it: font slugs (inter), groups (sans, serif, display, mono), and system. The CMS keeps one <link data-hcms-fonts> in the head that loads exactly the families the page’s font fields hold, and removes it when none do.
Variant (never inferred; set variant explicitly): a row of icon tiles, one per option. Each data-hcms-options token is value:icon or a bare value. Icons: cols-1, cols-2, cols-3, cols-4, media-left, media-right, media-top, list, cards, centered, card-flat, card-raised. Choosing one writes only the attribute, so the layout lives in your CSS:
<section id="features" data-layout="3" style="--color-section-bg: #f8fafc"
data-hcms-component:data-layout="variant"
data-hcms-options:data-layout="1:cols-1 2:cols-2 3:cols-3"
data-hcms-options:--color-section-bg="slate-50 slate-100 sky-50 custom">
#features { display: grid; gap: 1.5rem; }
#features[data-layout="2"] { grid-template-columns: repeat(2, 1fr); }
#features[data-layout="3"] { grid-template-columns: repeat(3, 1fr); }
The matching rules are "features": { "layout": "#features@data-layout", "background": "#features@--color-section-bg" }.
Several controls on one element. data-hcms-component:<prop>, data-hcms-options:<prop> and data-hcms-crop:<prop> apply only to the rule ending in @<prop>, and win over the plain attribute. A plain data-hcms-component or data-hcms-options never applies to an @--name rule.
Where they show up. In the sidebar, style fields bound to html, body or :root gather in a Page style group at the top of the form, and inside every other group and card the style fields come first. In the inline view they are never hover targets: a section’s style fields open from a Style chip in its top left corner, a list row’s from Style in its gear menu, and the page’s from the Page style button in the bar.
What a write accepts. Saves through the JSON API and other guarded writers accept a custom property only when its value is plain: at most 200 characters; no ;, {, }, <, > or backslash; balanced quotes and brackets; no comments or control characters; and no url(), image(), image-set(), cross-fade(), element(), paint(), src() or expression(). Writing back a value that is already there is always accepted.
Custom form templates#
Use a template when you need a control or form layout that the named components do not supply. Put it in the page’s body before opening the CMS; lookup uses data-hcms-tpl, not its position beside a field. This overrides the sidebar control for bio:
<template data-hcms-tpl="bio">
<label>
<span data-hcms-label></span>
<textarea data-hcms-field rows="6"></textarea>
<span class="hcms-error" hidden></span>
</label>
</template>
For a field or container, lookup uses the first matching template:
- Exact path, such as
links.0.label. - Wildcard path, such as
links.*.label. - Selected component or shape default.
By default, .photo@src selects @image, while a plain text field uses @scalar. Overriding @scalar does not replace an inferred image control; use a path-specific template or override @image for that.
| Template key | Scope |
|---|---|
links | This list’s container |
links.* | Each item in this list |
links.*.label | The label field within each item |
@scalar, @object | Default plain field or field group |
@scalar-array, @object-array | Default list containers |
@scalar-array-item, @object-array-item | Default list items |
@image, @textarea, and other component keys | Fields using that component |
List items have a separate lookup order:
- Wildcard item template, such as
links.*. - The item template for the component used by the list container, if it supplies one.
- Shape default, such as
@object-array-item.
A path-specific list container and its item template are separate overrides.
Leaf and object-list-item templates can supply their own fields. For a field group, use a slotted template with .hcms-object-fields; self-supplied child fields in a group template read as null and can erase the group’s page content on the next commit.
For scalar-list item layouts, retain the field used by the selected item default. With the standard scalar-list default, use input[data-hcms-field] in a per-list template such as tags.*. Replacing it with a textarea there can produce null items and erase their text after an unrelated edit. To change that field type, override @scalar-array-item instead; this affects every scalar list using that default.
The markers below identify the controls and slots for generated fields and list items.
| Marker inside the template | Purpose |
|---|---|
data-hcms-field | The element holding a scalar value: input, textarea, select, image, link, or contenteditable element |
data-hcms-field="label" | Bind a child field by key inside an object-list-item template |
data-hcms-label | Where the generated label appears |
data-hcms-shape="object-array" or "scalar-array" | Required on a custom list root for removal confirmation and item-limit handling |
data-hcms-action="add" or "remove" | List add or remove button |
data-hcms-action="move-up" or "move-down" | List reorder button |
.hcms-object-fields | Container for generated child fields in a slotted field-group template |
.hcms-card-fields | Container for generated child fields in a slotted object-list-item template |
.hcms-array-items | Direct child of the rendered list root; generated item roots must be direct children of this slot |
.hcms-error | Error display slot; retain it in custom controls |
A custom list container must preserve that direct-child relationship:
<template data-hcms-tpl="links">
<section data-hcms-shape="object-array">
<div class="hcms-array-items"></div>
<div class="hcms-error" hidden></div>
<button type="button" data-hcms-action="add">Add link</button>
</section>
</template>
After changing a template, compare clay.cms.api.getData() with the existing page content before editing or saving. Unexpected null values or empty lists indicate a binding problem; fix the template first. These mistakes can commit successfully and erase existing content, so do not rely on onError to catch them.
On a list’s <template>, data-hcms-min-items and data-hcms-max-items set the editor’s item limits. data-hcms-no-add, data-hcms-no-remove, and data-hcms-no-reorder hide those operations. These configure the editor, not server permissions or validation for writes outside it.
For per-list deletion confirmation, put data-hcms-confirm-remove on the root element inside the custom array template, not on <template> itself. That root must also carry the matching data-hcms-shape from the table above.
An empty confirmation attribute uses the default prompt. A string supplies the prompt; off, false, no, or 0 disables it, ignoring case and surrounding whitespace for those disabling values. This overrides the session’s confirmRemove option.
Images and cropping#
Map the source and opt into cropping on the image:
<img class="photo" src="portrait.jpg" alt="Portrait" data-hcms-crop="1:1">
{ "photo": ".photo@src" }
data-hcms-crop="1:1" requests a square crop. Use another width-to-height ratio or free for an unconstrained crop. An image field without that attribute does not automatically acquire cropping.
The upload destination belongs to the save host. Every host stores accepted uploads in its uploads folder, in a folder named after the document: profile.htmlclay uses uploads/assets-profile/. The returned link is the host path /_/uploads/assets-profile/<name>, served at every address of the host. A host without upload support may use an embedded data URL instead, increasing the HTML file’s size. An upload error is not the same as a successful fallback; check the displayed result.
JavaScript API#
These methods are available on clay.cms after clay.ready resolves:
| Member | Behavior |
|---|---|
open(options) | Open a session or switch its view; calling it for the active view is a no-op |
close() | End the session without saving the page |
refresh() | Refresh the active editor from the page |
isOpen | Boolean getter |
currentView() | "inline", "sidebar", or null when closed |
api.getData() | Current form data, or null when closed |
api.setValue(path, value) | Write a scalar leaf through the active session |
api.addItem(arrayPath) | Add an item to a list |
api.removeItem(itemPath) | Remove the indexed list item without asking for confirmation |
The action methods open, close, refresh, setValue, addItem, and removeItem return undefined, not a Promise or a success result. Observe changes and errors through the callbacks or events below.
For the list example, api.getData() returns:
{ "links": [{ "label": "My projects", "url": "https://example.com" }] }
Paths use dots and numeric indices: links.0.url. setValue accepts a scalar leaf, not an entire object or list. setValue, addItem, and removeItem throw synchronously when the session is closed. Invalid paths can also throw before a commit; onError does not catch those API-use errors.
confirmRemove and data-hcms-confirm-remove apply to the editor’s remove controls, not api.removeItem(). A custom delete button must ask for confirmation itself if you want one. The API still honors list limits: removal stops at data-hcms-min-items or data-hcms-no-remove; addition stops at data-hcms-max-items or data-hcms-no-add. Those blocked operations return without changing the page or throwing an error.
clay.ready.then(() => {
clay.cms.open({ view: 'inline' });
clay.cms.api.setValue('name', 'Alex R.');
clay.cms.open({ view: 'sidebar' });
});
Open options#
| Option | Default | Purpose |
|---|---|---|
view | Existing view, otherwise the page’s declared view (sidebar when none) | Select "inline" or "sidebar" |
rules | "cms" | A rules token or an explicit rules object |
pageRoot | document.body | Root for extracting and applying content |
richText | true | Allow rich text upgrading |
theme | null | "light" or "dark" in either view; otherwise follow the system preference |
confirmRemove | Confirmation for object-list cards, none for scalar lists including chips | For editor remove controls: true requests confirmation, false skips it, and a string supplies the prompt; a list’s data-hcms-confirm-remove overrides this option |
onChange(data, info) | None | Receive changed data and its path/structural metadata |
onError(error) | None | Receive commit errors |
For a successful CMS commit, info.path identifies the edited field or affected list/item path. info.structural is true for add, remove, and reorder operations, and false for scalar edits. Editing a link URL produces info such as { path: 'links.0.url', structural: false }. An undo or redo that changes CMS data also calls onChange, with path: "" and structural: false. Undoing unrelated page content does not call it.
Sidebar presentation options:
| Option | Default | Accepted value and behavior |
|---|---|---|
mountTo | document.body | A DOM element to contain the sidebar |
side | "right" | "left" or "right" |
overlay | false | Boolean; overlay the page instead of shifting it |
showSaveButton | false | Boolean; show a button carrying trigger-save. ClayJS handles that click in edit mode; a custom host must provide its own handler. |
title | "Page content" | String shown in the sidebar heading |
eyebrow | "Edit" | String shown above the heading |
Switching views carries the current session’s options. Close and reopen when you need to replace the session’s rules or root.
Events#
hcms:open, hcms:change, hcms:error, and hcms:close bubble to the document. Their detail includes pageRoot and view. Change events also include data, path, and structural; the last is true for operations such as adding or removing list items.
A view switch dispatches hcms:close with reason: "switch", then hcms:open for the new view. The open event’s previous value names the old view; it is null on the first open. Use these values to distinguish switching from closing and reopening.
Undo and redo that change CMS data call onChange, but do not dispatch hcms:change.
Error events include error. Commit error objects identify their kind in error.name, such as EmptyListInsert. Commit failures also include attemptedData, the form data the CMS tried to apply to the page. Inspect it when diagnosing a rejected edit; correct the cause before retrying.
Failed structural commits restore a snapshot of the affected list container’s content, or non-editor page content when that container cannot be resolved. Scalar commits do not take a rollback snapshot. After an unexpected scalar failure, inspect the affected content before retrying; do not assume every failed commit is atomic.
Upload failures dispatch hcms:error, not onError. Read event.detail.code and event.detail.path; the code is not a property of the error object. The path identifies the affected field. Common codes are too-large for the host’s size limit, unsupported-type for a rejected file type, and timeout when the result is unknown. The code comes from the uploader’s result, so handle unknown codes as a generic upload failure. For the local retry procedure, see guide troubleshooting.
Use these events to observe the session. They do not replace the host’s save result.
Saving and ownership#
The CMS edits the DOM. ClayJS and the host handle saving. Use Cmd+S on macOS or Ctrl+S on Windows and Linux, or add the autosave attribute to <html>. Add indicator to the loader’s plugin list for visible save feedback, as in the guide. Runtime editor elements and session markers are removed by the snapshot pipeline, so they do not become the saved content.
The saved document retains the content and the configuration needed to edit it again. Uploaded assets may remain separate files. Moving the HTML alone does not move those assets.
Host permission determines who can persist changes. Showing editing controls is not an access-control boundary. The same distinction applies to collaboration: CMS fields can refresh when the page changes, but live synchronization requires a host and runtime that provide it.
Configure with a coding agent#
On Hyperclay, give an AI coding agent your HTML and this request:
Add Hyperclay CMS rules for the human content in this page. Preserve its layout, styles, scripts, and existing content. Map content such as headings, body text, image sources, and link destinations to its elements. Load the CMS through ClayJS. Do not map navigation controls or application machinery as editable content. Use this reference: /docs/plugins/cms/. Show me the changes.
Review the rules and try each field. Generating configuration does not verify that its selectors point at the content you intended.