# data: pages as JSON

> Read the live page into structured JSON and apply JSON back to it, atomically.

Source: https://clayjs.com/docs/plugins/data/

`clay.extractData(rules?)` reads your live page into structured JSON, and `clay.applyData(values, {rules})` applies content-only JSON changes atomically. Core adds `clay.readData()` and `clay.writeData()` for hosts that announce the matching saved-file capabilities.

## Loading

```html
<script src="https://clayjs.com/v1/clay-data.js"></script>
```

Both come from the `data` plugin: it loads by name (`?plugins=data` on the clay.js tag) or as this satellite. Both attach to `window.clay`. From an inline script, wait for the tag first: `await clay.loaded.data`, then `const data = clay.extractData()`.

## 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"}]}
```

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

```js
const rules = { title: "h1", items: ".todo[]" }
const data = clay.extractData(rules)   // reads the live page
clay.applyData(data, { rules })        // writes values back into the page, atomically
```

`extractData` reads the live page, so it sees unsaved edits, which makes it handy right before a save or a POST. You can also pass rules inline; with no argument it uses the page's `api` rules tag:

```js
clay.extractData()                 // the page's api rules tag
clay.extractData({ title: "h1" })  // rules passed inline
```

`applyData` mutates the local page only. It is not a server write. On Hyperclay, save the page the normal way to persist it, and use [Collections](https://hyperclay.com/docs/collections/), which has its own write endpoint, to store submissions from visitors.

### 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:

```html
<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">
  await clay.loaded.data
  clay.applyData({ name: "Ada", bio: "Builds things." })

  document.getElementById('profile').addEventListener('submit', (e) => {
    e.preventDefault()
    const data = clay.extractData()   // read the form back out
    // ...send data somewhere, or save the page
  })
</script>
```

This is the pattern the seeded [Collections](https://hyperclay.com/docs/collections/) form uses.

## From the page itself

To try it without writing code, download the [JSON demo](https://panphora.hyperclay.com/_/download/demos/ai-native-html-files/json-api.htmlclay?file=1) and open it with HTML Clay. Edit the hobbies, then click Get JSON to read the saved file. The result stays visible until you click Get JSON again.

Switch to the Set JSON tab to see the complete JSON with Change the title beside the title and Add a hobby beside the list. Each button sends a real request and updates both the JSON and the page. Titles cycle through 20 options, and you can keep adding hobbies. The page updates alongside the JSON panel on desktop; on mobile, the page and panel stack. The request endpoint appears below each section after you use it.

Code in the todo page can read its live content, read the saved file and write changes back:

```js
(async () => {
  await clay.ready;
  await clay.loaded.data;
  const rules = { title: "h1", items: ".todo[]" };
  const live = clay.extractData(rules);
  const saved = await clay.readData(rules);
  await clay.writeData({ ...saved.data, title: "Tomorrow" }, {
    rules,
    ifMatch: saved.etag
  });
})();
```

Save ordinary page edits before calling `clay.writeData()`. After the write, ClayJS updates the open page without a reload.

## Save the rules in the file

When several programs use the same file, put the rules in it once. Add this tag inside the file's `<head>`:

```html
<script type="application/json" data-rules-name="api" data-rules-version="1">
{
  "title": "h1",
  "items": ".todo[]"
}
</script>
```

Now ask for the file through `/_/api/`, with no `data` parameter. Put `/_/api/` between the port and the file name:

```bash
api="$base/_/api/todo.htmlclay"
curl "$api"
```

`/_/api/` reads the tag named `api` and returns the same JSON. Writes work the same way, without the rules in the URL:

```bash
curl "$api" \
  -H 'Content-Type: application/json' \
  --data '{"title":"Tomorrow","items":["Buy bread","Book tickets"]}'
```

### Which rules apply

Rules sent with a request replace the file's tag for that request, on either URL. They are never merged with the tag. `/_/api/` uses the tag only when the request has no `data` parameter.

An empty, malformed or repeated `data` parameter returns `400`. HTML Clay never falls back to the tag.

## Rules reference

Each rule selects a value from the file. These forms work in the `data` parameter or the rules tag:

| Form | Example | Value read |
| :-- | :-- | :-- |
| `sel` | `"h1"` | Text of the first match |
| `sel[]` | `".todo[]"` | Text of every match, as an array |
| `sel@attr` | `".todo@data-done"` | An attribute on the first match |
| `[sel, {...}]` | `[".todo", {"text": ".", "done": "@data-done"}]` | One object per match |

To read text and completion together, use these rules:

```json
{
  "title": "h1",
  "items": [".todo", {"text": ".", "done": "@data-done"}]
}
```

Inside each task, `.` reads its text and `@data-done` reads its attribute. The next read returns:

```json
{
  "title": "Tomorrow",
  "items": [
    {"text": "Buy bread", "done": "no"},
    {"text": "Book train tickets", "done": "no"}
  ]
}
```

Use that object shape for subsequent writes too. To mark the first task done, send this body:

```json
{
  "items": [
    {"text": "Buy bread", "done": "yes"},
    {"text": "Book train tickets", "done": "no"}
  ]
}
```

## Read and write a document as JSON

Each file can expose a JSON endpoint. GET reads mapped values; POST writes mapped values back into the HTML file. Your script does not need a browser or its own HTML parser.

```html
<h1>Hello</h1>
<ul><li class="i">one</li><li class="i">two</li></ul>
<script data-rules-name="api" data-rules-version="1">
  {title: "h1", items: ".i[]"}
</script>
```

```
$ curl http://htmlclay.localhost:1212/_/api/notes.htmlclay
{"title":"Hello","items":["one","two"]}
```

**The same file boundary applies.** A data request can read only what a normal request could serve.

**Reading does not change the file.** GET creates no editing session, save token or version. POST changes mapped values in a writable file. Include the optional `If-Match` header to reject a write if the file has changed since you read it. See the [JSON API guide](https://clayjs.com/json-api) for the request format.

The API uses the same rules dialect as the Hyperclay platform, so a rule written for either one works with the other. A caller can also pass ad hoc rules in the URL with `?data={...}`. The API sends no CORS headers, so a page served by another local server on your machine cannot read your files.
