Working with content

Editing page builders

How an agent changes one block of a long page without holding the whole page.

On this page

Page builders don’t cost the whole page per edit. An agent reads an index of the blocks, fetches the one it needs, and sends back only what changed. The server merges that into the stored page by id, so the other blocks keep their content, including changes someone made in the Control Panel in the meantime.

How an edit works#

Step through it. The arguments and responses were recorded from the real server, on a page with 16 blocks.

A page with 16 blocks
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
Read an index of the blocks
1,241 tokens read

A large Replicator field comes back as an index: each set's id, type, and a short preview. The agent sees the page without holding it.

entries_get arguments
{
  "collection": "landing",
  "slug": "tours"
}
response
{
  "id": "cd6fb094-92a7-4293-a2b0-1e46ac56a597",
  "collection": "landing",
  "blueprint": "landing_page",
  "slug": "tours",
  "site": "en",
  "status": "published",
  "published": true,
  "url": "/landing/tours",
  "format": "raw",
  "data": {
    "title": "Tours",
    "page_builder": {
      "__preview": "16 sets",
      "truncated": true,
      "sets": [
        {
          "id": "hero0001",
          "type": "section_hero",
          "preview": "Bike tours along the Silver Coast"
        },
        {
          "id": "text0002",
          "type": "section_text_block",
          "preview": "Block 2 describes one part of the tour in detail, with pract..."
        },
        "… 14 more sets"
      ],
      "note": "NOT writable — fetch one set with entries_get fields: [\"page_builder.<id>\"], or the whole field with fields: [\"page_builder\"]. The update tool merges into one set with {\"page_builder\": {\"<id>\": {...}}}, and adds, removes, or reorders sets with the list of ids in their new order, a new set where it goes: [{\"id\": \"<id>\"}, {\"type\": \"...\", ...}]"
    },
    "body": {
      "__preview": "The coastal route runs north from the harbour, past the lighthouse and along the dunes. Ev…",
      "truncated": true,
      "note": "NOT writable — fetch raw field before editing: entries_get with fields: [\"body\"]"
    }
  },
  "cp_edit_url": "https://example.com/cp/collections/landing/entries/cd6fb094-92a7-4293-a2b0-1e46ac56a597"
}

The token counts are the package’s own measurements of a page with 16 blocks. Changing one title this way cost 1,968 tokens in total, counting the 100-token result of entries_update, against 14,050 for reading and resending the whole page builder.

What large values come back as#

entries_get and terms_get shorten large values unless the agent asks for them with fields.

ValueComes back as
Bard or Markdown over 500 bytesA preview
Any other value over 4 KB, such as a synced array fieldA preview
A large Replicator fieldAn index of its sets, each with its id, type, and a short preview
A GridInline, whatever its size

Each shortened value says what to do next, so the agent isn’t left guessing.

{
  "__preview": "16 sets",
  "truncated": true,
  "sets": [
    { "id": "hero0001", "type": "section_hero", "preview": "Bike tours along the Silver Coast" }
  ],
  "note": "NOT writable — fetch one set with entries_get fields: ["page_builder.<id>"], or the whole field with fields: ["page_builder"]. …"
}

fields: ["page_builder"] returns the whole field. fields: ["page_builder.<id>"] returns one set of a Replicator or Grid field, as an object keyed by its id, which is the shape entries_update merges by id.

The merge rules#

entries_update does a shallow top-level merge of raw data, so nested structures are replaced whole. Replicator and Grid fields are the exception, because their sets are addressed by id.

An object keyed by set id#

Merges into just those sets.

{ "page_builder": { "wKJQXf2s": { "variant": "centered" } } }
  • Send only the fields you change, and null to remove a set.
  • Inside a set the merge is shallow as well. Each named field is replaced whole, a Group included, and null clears it.
  • A Replicator or Grid nested in the set merges by id the same way.

A list#

Sets the order.

{ "page_builder": [{ "id": "wKJQXf2s" }, { "type": "faq", "question": "…" }, { "id": "3kPq9xYz" }] }
  • An item with a stored id merges into that set, so {"id": "wKJQXf2s"} keeps it as it is.
  • An item in another type replaces its set.
  • An item without an id is a new set, and the result lists its id under added.
  • Sets you leave out are removed.
  • A new set sent with a blank id, 0, or an id another new set also has gets a fresh id. This also applies through the create tools, terms_update, and globals_update.
  • A bare id the field doesn’t store is refused, so a block someone removed in the meantime never comes back empty.

Clearing a field#

null clears the field. An empty list or object is refused, because PHP can’t tell {} from [], and {} would wipe the page.

Find and replace#

A Bard, Markdown, text, or textarea field also takes a find and replace, so fixing a sentence never costs the whole article.

{ "article": { "find": "…", "replace": "…" } }

find must match the stored text exactly once. For Bard it must match inside one formatting run, because bold, a link, or a line break ends a run. It works inside a set addressed by id too, and in terms_update and globals_update.

A miss or several matches is an error that says what went wrong, and multiple matches quote the nearby text.

find matches nothing in title — it must match the stored text exactly once; read the field to copy it
find matches 12 times in page_builder.text0002.text — add words around it so it matches once: …es one part of the tour in detail, with practical notes for riders. Block 2 describes one part … | …

Validation#

The whole field is validated and saved as the Control Panel saves it.

  • Whatever a set already stores passes, including keys its blueprint no longer defines and sets of removed types.
  • A block that no longer passes its blueprint’s rules blocks the write, and the error names it by id, as in page_builder.wKJQXf2s.heading.
  • An update that changes nothing saves nothing and creates no revision.
  • Every unknown key is reported in one error.
unknown field no_such_field — valid handles: body, facts, hero, page_builder, seo, starts, title

What it saves#

Write results also carry no copy of what was just written, so an agent doesn’t pay to read back its own edit.

What everyday tasks cost an agent Tokens the model reads and writes over the tool calls of one task.
0.7: whole values in and out 0.9: ids, previews, and merges by id
  • Read an entry with a large synced field 32× less
    24.4k
    758
  • Add a block to a page 7.3× less
    11.1k
    1.5k
  • Edit one field of one block 7.1× less
    10.7k
    1.5k
  • Add a link to a menu 1.5× less
    4.7k
    3.1k
  • List 25 images 1.6× less
    2.3k
    1.5k
  • Change one global setting 1.2× less
    1.7k
    1.4k

Published by the package. Counted with the cl100k tokenizer over HTTP, for the tool calls and results of each task, on pages with 8 to 16 blocks. On 0.7, each page edit is counted at a single whole-page write.