clay.js
  1. ClayJS
  2. Reference
  3. Hosts and protocols
  4. Host compatibility

Host compatibility

Which features each host supports across Hyperclay, HTML Clay, Hyperclay Local, and your own server, and how ClayJS discovers host capabilities.

ClayJS runs in the browser, while a host serves the HTML file and accepts saves. Every ClayJS document works across Hyperclay, HTML Clay, Hyperclay Local, and your own server, and ClayJS discovers which optional features the current host supports at runtime.

A plain static server or a file:// URL can open a document in view mode, and the demo plugin can simulate saving into browser storage, but saving back to the file requires a host that implements POST /_/save.

Feature support by host#

FeatureHyperclayHTML ClayHyperclay LocalYour own server (Host it yourself)
Who editsOwner, team editors, edit-link holdersThe local userThe local user, plus teammates through device syncWhoever your server allows (set window.clayEditMode = true in the page)
Saving (POST /_/save)YesYesYesYes (the only required route)
Max save size5 MB50 MB20 MBUp to your server
Conditional saves and receiptsYesYesYesWhen /_/meta announces conditional and returns document.saveId or save-history
Live sync (sync plugin)Every editor and viewerOpen tabs on this computerOpen tabs on this computer, plus other devices via syncWhen your server implements /_/sync and announces sync
Outside disk edits reach open tabsNo disk except through device syncYes (agents, text editors, Git)YesWhen your server watches disk and broadcasts on /_/sync
PresenceAvatar stack (no JS API)NoneNoneWhen /_/meta announces presence
by on sync frames and editing markerYesNoNoWhen /_/meta announces presence-editing
Live stores (live plugin)Every editor at full speedTabs in one browser at full speedTabs in one browser at full speed; other devices at save speedTabs in one browser at full speed, or across clients with live-data
Page editor merge stateKept beside the fileShared across open tabs; rebuilt from the article on fresh openKept beside the fileWhen /_/meta announces page
Page recoveryYesYesYesWhen /_/meta announces page-recovery-v1 and names document.pageState.href
wire and ai-editai-edit only, for owners and team editors on a paid planBoth wire and ai-editBoth wire and ai-editWhen /_/meta announces wire and lists helpers in document.helpers
Data API (readData / writeData)Read only (data-read)Read and write (data-read, data-write)Read and write (data-read, data-write)When /_/meta announces data-read and data-write
Uploads (upload plugin)10, 20, or 100 MB by plan, in account uploads/25 MB, in ~/htmlclay/uploads/25 MB, in the folder’s uploads/When /_/meta announces upload and your server implements POST /_/upload
Works offline (Offline in a local host)NoYes: one bundled ClayJS release at /_/clayjs/<version>/, including older Journals’ exact library and font URLsYes, the sameWhen your server bundles a release the same way
Version historyOne version per save (reload open editor tabs after restoring)Yes60 days, newest 20 versionsUp to your server

How ClayJS learns what a host supports#

ClayJS never infers host capabilities from the hostname, port, or trial requests. Instead, it reads capabilities from the GET /_/meta discovery endpoint and from attributes the host stamps on <html> when serving the page.

Capability discovery (GET /_/meta)#

When a feature needs to know what the host supports, ClayJS sends a GET request to /_/meta (or /_/meta/{token} when <html savetoken> is present) with the Document-URL header set to window.location.href, cache: "no-store", redirect: "manual", and a 6-second timeout. In visitor draft mode, ClayJS skips discovery and treats the host as a bare core host immediately.

Only a 2xx JSON response with a numeric spec property is accepted as a capability document:

JSON
{
  "spec": 1,
  "extensions": ["conditional", "save-history", "sync", "upload", "data-read", "data-write", "wire", "people"],
  "document": {
    "etag": "\"8f02c16e7b4d9a30\"",
    "saveId": "b3f1c8e2-...",
    "upload": { "maxBytes": 26214400 },
    "helpers": [{ "name": "ai-edit", "state": "ready" }],
    "people": { "me": { "id": "u1", "name": "Ada" } }
  }
}

If /_/meta returns 404, a redirect, an HTML error page, or any non-JSON body, ClayJS treats the server as a bare core host ({ spec: null, extensions: [], document: null }) that supports only POST /_/save. Discovery never throws or rejects.

Extension names in extensions#

ExtensionWhat it enables in ClayJS
conditionalSends If-Match on saves and handles HTTP 412 conflicts (see Conditional saves and receipts).
save-historyQueries /_/meta with Save-ID and Save-Request-Hash (document.saveOutcome) after a timed-out save, and enables history for clay.live.
data-readAllows clay.readData() to read the saved file as JSON.
data-writeAllows clay.writeData() to write JSON into the saved file.
uploadAllows clay.upload() to post files to /_/upload (when document.upload is also present, pre-checking document.upload.maxBytes).
sync, sync-worker, sync-worker-dataSelects /_/sync as the live sync transport (without a spec response, sync falls back to legacy /_/live-sync/save and /_/live-sync/stream).
presence, presence-editingEnables the presence avatar stack and active-block editing outlines in sync.
live-data, live-provenanceEnables host relay and provenance for clay.live and page.
pageEnables host-backed merge state for the page plugin.
page-recovery-v1With document.pageState.href, reads the page state stored for the exact saved bytes and may retire journaled page work a proven checkpoint covers; without either, the records stay.
peopleReads document.people for clay.me and clay.people.
wireEnables clay.wire and reads document.helpers ({ name, state } with state ready, denied, or unavailable). ai-edit activates when document.helpers lists "ai-edit" as ready or unavailable.

The page-state resource#

document.pageState.href names the route that returns the page editor’s stored Yjs state for the exact bytes a client accepted. ClayJS asks it only when an accepted save’s own capture cannot prove a checkpoint, and it asks conditionally:

  • The request is a GET with If-Match set to the ETag that save carried, Document-URL set to window.location.href, cache: "no-store" and redirect: "manual".
  • 428 means no condition was sent, 412 means another version of the document is stored, and 404 means the host keeps no page state for these bytes. Each of them, and every other failure, keeps the journal exactly as it was.
  • A 2xx answer carries version at 1, documentId, generation, etag, stateHash and tag. ClayJS accepts it only when the document identity, the generation and the ETag match the ones it accepted, when the digest of the decoded state matches stateHash, and when that state projects to the article the save sent.
JSON
{
  "version": 1,
  "documentId": "b3f1c8e2-4d1a-4f8e-9c2b-7a6d5e4f3c21",
  "generation": "9c1f4a7e2b8d4c6f0a3e5d7b9c1f2a4e",
  "etag": "8f02c16e7b4d9a30",
  "stateHash": "5f0c1d8a3b7e94f2c6d05a1e8b3f7c9d2e4a6b8c0d1e3f5a7b9c1d2e3f4a5b6c",
  "tag": "{\"v\":1,\"g\":0,\"y\":{\"seed\":\"...\"}}"
}

Hyperclay and Hyperclay Local answer /_/page-state under the document lock, and HTML Clay answers /_/page-state/{token} for the token in <html savetoken>. Hyperclay names its document row id and HTML Clay the id behind its history key; Hyperclay Local mints no document id at all and answers documentId: null.

Host-injected attributes on <html>#

A host can also stamp three attributes onto the root <html> element when serving a document. ClayJS reads them once at boot and strips all three before every save:

  • savetoken: Arms edit mode and sends requests to /_/save/{token}, /_/meta/{token}, and /_/upload/{token}. (If a host sends only the legacy pre-1.9.0 htmlclaytoken attribute, ClayJS logs a warning and keeps edit mode off.)
  • documentetag: Seeds the initial ETag stamp for the version of the file that was served.
  • sitevisibility: Sets clay.siteVisibility to "private" or "public" (or null when absent).