You're reading docs for Nextly Alpha. APIs may change between releases.

Page Builder

Compose pages through the API

Insert patterns, place components, edit a component's fields and variants, and choose a page's Layout from a script, with a dry run before each change.

Everything an editor does to compose a page can be done through the API: insert a pattern, place a component, change a placement's values or variant, choose a page's Layout, and edit a component's exposed fields and variants. A script or an agent can ask what a change would do before making it.

These routes are experimental: their requests and answers may still change before they are marked stable.

The routes are plugin routes under /admin/api/plugins/@nextlyhq/plugin-page-builder. They run as the caller, signed in or with an API key, with the same checks as the admin, and answer in the usual { message, item } shape.

How every action works

  • dryRun: true answers what the action would write, and whether the site would accept it, and writes nothing. The write is made as the caller and rolled back, so the site's own checks answer: field rules over the whole page, locks, component loops and the caller's rights. Why the site would refuse it is in item.refusals, empty when it would accept it. The answer shows the item as the save would store it, after the site's hooks.
  • Hooks run for a dry run's write too, with context["pageBuilder.transferJudgement"] set to true. A hook with an effect a rollback cannot undo, such as sending a message, should skip it then and act on the write that commits.
  • item.fingerprint names the stored item the answer was planned against. Send it back as expect and the action applies only to that item: one that changed since answers 409; plan again. An action is also refused with 409 when someone else saves the item between the action's read and its write.
  • item.by says who the action ran as: { kind: "user", userId }, or { kind: "api-key", userId, apiKeyId }. A change made by a signed-in person is recorded against them on the item's content events.
  • publish: true publishes the item with the change and needs the publish right. Without it the change is saved to the item's working draft, as the editor saves it.

An action the site refuses answers 422, with each reason in the error's data.errors, and writes nothing.

Change a page

POST /admin/api/plugins/@nextlyhq/plugin-page-builder/pages/actions
Content-Type: application/json

{
  "page": { "collection": "pages", "id": "<page id>" },
  "action": { "kind": "insert-pattern", "patternId": "<pattern id>" },
  "dryRun": true
}

page may also name the blocks field (content by default) and, for a translated field, the locale; a field that holds no blocks is refused. The caller needs update on the page's collection. A collection with a Draft/Published lifecycle but no working drafts saves an edit straight onto the published entry, so there the caller needs publish too. action is one of:

kindFieldsWhat it does
insert-patternpatternId, at?Copies a published pattern's blocks into the page, as the editor's insert panel does.
place-componentcomponentId, variant?, values?, at?Places a component by reference, with a variant's id and values keyed by exposed field id.
set-placement-valuesinstanceId, values?, reset?Sets a placement's values. { "$unset": true } empties a field; reset lists fields to restore.
choose-variantinstanceId, variantChooses a variant by its id, or null for the component's defaults.
set-layoutlayout, noHeaderFooter?Chooses the page's Layout by id, or null for the site default. noHeaderFooter: true switches it off. Pages collection only.

at is { "index": 2 } for the page's top level, or { "parentId", "slot", "index" } inside a block; without it the block goes at the end of the page. The answer holds the ops the change makes, the page's blocks as it leaves them (document) or its Layout fields (layout), and insertedId, the new block's id for the next action to address. New blocks get fresh ids on every write, as in the editor, so the ids in a dry run's answer are not the ones an apply stores: address what the apply's own answer names.

Change a component's definition

POST /admin/api/plugins/@nextlyhq/plugin-page-builder/components/:id/definition
Content-Type: application/json

{ "action": { "kind": "add-variant", "label": "Dark" }, "dryRun": true }

The caller needs update on components. Each edit is held to the rules of the component's own view: a variant's label is unique, ids are stable and never handed out twice, a removed field's id is never reused, and a value must fit its field.

kindFields
exposelabel, nodeId, and propPath or visibility
rename-exposureexposureId, label
retarget-exposureexposureId, nodeId, and propPath or visibility
remove-exposureexposureId
remove-exposed-slotslotId
add-variantlabel
rename-variantvariantId, label
delete-variantvariantId
set-variant-valuevariantId, exposureId, value
reset-variant-valuevariantId, exposureId

The answer holds the definition as the edit leaves it, targetId (the field, slot or variant made or changed), warnings, and, when a removed field emptied variants, prunedVariants. A refused edit answers 422 with the planner's reason as the error code, such as label-taken.

Other routes with a dry run