Every page, a JSON API
Every Hyperclay page is already structured data: the HTML is the database. This page is the API for reading and writing that data. You describe what counts as data with a small map from field names to CSS selectors, and that one mapping works three ways:
- From the URL, as an ad-hoc query (
?data=), read only, no setup. - As a named endpoint the page publishes about itself (
/_/api), read only. - In the browser, as
extractDataandapplyData, which read and write the live page.
The selector syntax is identical across all three, so once you can write the mapping you can use any of them.
Read any page from the URL
Append ?data= to any site URL with a mapping, and Hyperclay returns JSON. The rules live in the URL, nothing is set up in advance, and it is read only.
<h1>My Tech Blog</h1>
<article class="post">
<h2 class="post-title">Understanding JavaScript</h2>
<span class="author">Alice Smith</span>
</article>
<article class="post">
<h2 class="post-title">Python for Data Science</h2>
<span class="author">Bob Johnson</span>
</article>?data={title:"h1",posts:[".post",{heading:".post-title",author:".author"}]}{
"title": "My Tech Blog",
"posts": [
{ "heading": "Understanding JavaScript", "author": "Alice Smith" },
{ "heading": "Python for Data Science", "author": "Bob Johnson" }
]
}See it on a real site: panphora.hyperclay.com/core/panphora.html?data=…
Responses are cached for five minutes. The X-Cache header is HIT from cache, MISS for a fresh extraction.
The mapping syntax
The same grammar describes the mapping everywhere, in the URL, in a rules tag, or passed to the helpers.
| Pattern | Meaning |
|---|---|
"h1", ".class", "#id", "[attr]" | match by tag, class, id, or attribute |
"." | the current element, useful inside iteration |
".price@data-cents" | an attribute or property instead of text |
"input@value", "@checked", "a@href" | form values, properties, and links |
".tag[]" | every match, as an array of values |
[".post", { ... }] | iterate elements, extract a shape from each |
{ meta: { ... } } | nest objects freely |
A bare selector reads an element’s text. Add @name to read an attribute or property instead, which is how you get form values (@value), checkboxes (@checked), links (a@href), and custom attributes (.date@datetime). Selectors inside an iteration are scoped to each matched element.
In a ?data= URL every selector must be a quoted string, and the URL carries no spaces or line breaks. The formatted version is only for readability:
?data={posts:[".post",{title:".title",date:".date"}]}Publish a named endpoint
Instead of putting rules in the URL, bake them into the page so it advertises its own stable shape. Add a rules tag with the token api:
<script data-rules-name="api" data-rules-version="1" type="application/json">
{ "title": "h1", "posts": [".post", { "heading": ".post-title", "author": ".author" }] }
</script>The page now serves that JSON at its own URL under /_/api:
GET https://you.hyperclay.com/_/api/<path>/<sitename>.htmlFor a site named blog that is https://you.hyperclay.com/_/api/blog.html. The response is the bare JSON, public, GET only, and CORS-enabled, so any app on any origin can read it. data-rules-version must be "1", and one tag can serve several consumers with space-separated tokens, for example data-rules-name="api cms".
It is the same engine as ?data=. The only difference is where the rules come from: ?data= is a question you ask from outside, /_/api is the answer the page publishes about itself.
Read and write in the browser
The same mapping runs inside the page through two helpers. extractData reads values out of the live DOM into an object, and applyData writes an object back into the live DOM.
const data = hyperclay.extractData() // auto-detects the page's rules tag
hyperclay.applyData(document.body, data) // writes values back into the pageextractData reads the live page, so it sees unsaved edits, which makes it handy right before a save or a POST. You can also target a named tag or pass rules inline:
hyperclay.extractData('api')
hyperclay.extractData({ title: "h1" })applyData mutates the local page only. It is not a server write. To persist, save the page the normal Hyperclay way. To store submissions from visitors, use Collections, which has its own write endpoint.
Round-trip a form without a backend
Together the helpers move data between a form and a plain object with no server code. Use @value for inputs, since a bare selector reads text:
<form id="profile">
<input class="f-name">
<input class="f-bio">
</form>
<script data-rules-name="api" data-rules-version="1" type="application/json">
{ "name": ".f-name@value", "bio": ".f-bio@value" }
</script>
<script type="module">
hyperclay.applyData(document.body, { name: "Ada", bio: "Builds things." })
document.getElementById('profile').addEventListener('submit', (e) => {
e.preventDefault()
const data = hyperclay.extractData() // read the form back out
// ...send data somewhere, or save the page
})
</script>This is the pattern the seeded Collections form uses.
Loading the helpers
extractData and applyData ship in the standard, smooth-sailing, and everything presets, exposed as hyperclay.extractData and hyperclay.applyData. To import them directly, use the lean data module, not the full package entry, which pulls dependencies that do not resolve in the browser:
import { extractData, applyData } from 'https://cdn.jsdelivr.net/npm/hyper-html-api@0.4.0/src/data.js'Responses and limits
- A matched value comes back as text, a missing element as
null, an empty array as[]. - Failures return an HTTP error status with a JSON
{ "error": "..." }message. ?data=and/_/apiread the saved page on the server: static content only, no JavaScript execution, text and attributes rather than raw HTML.extractDataandapplyDatarun in the browser against the live page and can address element properties such as@valueand@checked.applyDatawrites the local DOM only.