Skip to Content
DocumentationCollections

Collections

Mark any folder as a collection and Hyperclay turns it into a place to gather submissions: a public form anyone can fill out, and a private dashboard only you can see. Every submission is saved as a JSON record inside your own folder, so the data is yours and travels with your account.

Hyperclay seeds a working form and dashboard for you, but they are just plain HTML files. You can edit them, swap them out, or build your own from scratch against the same API. This page is that API.

Mark a folder as a collection

In your file manager, open a folder’s menu and choose Mark as Collection. Hyperclay adds three things inside it:

  • submission-form.html, a public form,
  • dashboard.html, an owner-only dashboard,
  • a records/ folder, where submissions are stored as JSON.

To build your own pages you need two values. Open the collection’s menu and choose Collection info:

  • Collection ID (looks like col_a1b2c3xy): the public id for the API. It is stable, so it never changes even if you rename or move the folder.
  • Submit token: the credential a public form sends to create a submission. You only need this if you build your own form.

The API

Every endpoint is JSON over the same origin as your site. Send and read application/json. A plain HTML form post will not work, you submit with fetch.

MethodPathWhoPurpose
POST/_/collection/<id>/recordsanyone with the tokencreate or update a submission
GET/_/collection/<id>/records?limit=&offset=owner onlylist submissions
GET/_/collection/<id>/records/<key>depends on the recordread one submission
PUT/_/collection/<id>/records/<key>owner or the record’s ownerupdate one submission
DELETE/_/collection/<id>/records/<key>owner onlydelete one submission

<id> is the Collection ID from the modal. In a list (the owner GET that returns records), each record comes back as:

{ "id": "...", "data": { }, "modifiedAt": "2026-06-01T...", "size": 128 }

A single-record read returns { "id": "...", "data": { }, "modifiedAt": "2026-06-01T..." }, without size. In both cases data is whatever JSON you submitted. Success responses are { "ok": true, ... }. Failures return a non-2xx status with { "ok": false, "error": "..." }.

Limits: 1MB per submission, 30 requests per minute per IP, and 10,000 records per collection. Records count toward your account’s file storage.

Build your own form

The simplest public form collects some fields and POSTs them as data with the submit token. Hyperclay gives each submission an unguessable id and remembers it in a cookie, so the same browser can come back and edit its own submission later.

<form id="form"> <input name="name" placeholder="Your name"> <textarea name="message" placeholder="Your message"></textarea> <button>Send</button> </form> <script type="module"> const ID = 'col_a1b2c3xy' // Collection ID, from the folder's "Collection info" const TOKEN = 'paste-token-here' // Submit token, same place const form = document.getElementById('form') form.addEventListener('submit', async (e) => { e.preventDefault() const data = Object.fromEntries(new FormData(form)) const res = await fetch(`/_/collection/${ID}/records`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ token: TOKEN, data }) }) if (res.ok) form.replaceWith('Thanks, we got it.') else alert('Something went wrong, please try again.') }) </script>

data is just an object. You decide its shape. There is no server-side schema to define.

Let submitters come back and edit (optional)

Pass a key you choose (an email, a phone number) plus a code (a secret the submitter picks). The record is then identified by the key and protected by the code: the submitter can reload it from any device by re-sending the key and code, and nobody else can read or change it. The code is never stored, only a hash of it.

body: JSON.stringify({ token: TOKEN, key: email, code: secret, data })

To load an existing keyed record back into a form, GET it with the code in a header so it stays out of the URL:

const res = await fetch(`/_/collection/${ID}/records/${encodeURIComponent(email)}`, { headers: { 'x-collection-code': secret } }) const { ok, data } = await res.json()

Build your own dashboard

A dashboard is even simpler, because when you (the owner) view a page on your own site, your login cookie rides along automatically. No token, no auth code:

<div id="list"></div> <script type="module"> const ID = 'col_a1b2c3xy' const res = await fetch(`/_/collection/${ID}/records?limit=200`) const { records } = await res.json() document.getElementById('list').innerHTML = records.length ? records.map(r => `<pre>${JSON.stringify(r.data, null, 2)}</pre>`).join('') : 'No submissions yet.' </script>

Editing and deleting are one call each:

// update a record's data await fetch(`/_/collection/${ID}/records/${encodeURIComponent(key)}`, { method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ data: updated }) }) // delete a record await fetch(`/_/collection/${ID}/records/${encodeURIComponent(key)}`, { method: 'DELETE' })

A PUT replaces the whole data object. If you are only changing some fields, read the current record first, merge your changes, and send the result back.

Live updates (optional)

To make a dashboard update itself as submissions arrive, subscribe to the live-sync stream and listen for collection-record events. Open the stream only after a successful owner load, so a visitor who lands on the page never tries to connect.

const es = new EventSource('/_/live-sync/stream?page-url=' + encodeURIComponent(location.href)) es.addEventListener('collection-record', (e) => { const { op, id, data, modifiedAt } = JSON.parse(e.data) // op is 'create', 'update', or 'delete'. Update your list in place. })

Pausing and unmarking

The folder menu also lets you pause submissions (the form starts returning a “not accepting” error) and unmark the collection (the form and dashboard stop working, but your files and records are kept, and you can re-mark anytime).

If you hand out a form link, remember that the link is just the page’s file URL. Renaming or moving the file, or unmarking the collection, breaks that link. The Collection ID itself never changes, but the page URL is whatever you have named the file.

Working with the data elsewhere

Records are ordinary JSON files in your records/ folder, so anything else you build on Hyperclay can read them, and they sync to the desktop app like the rest of your files. The extractData and applyData helpers from the JSON API are a convenient way to move values between a form and a record object, but they are optional. A record is just JSON you control.

Last updated on