Skip to main content

Add Page Element

POST 

/v1/:slug/boards/:token/page_layout/elements

Inserts an element into a slot, or into a section column when parent_element_id is given.

Answers 200, not 201 — and not only by convention. element.id is an idempotency token: if you supply one and an element with that id already exists carrying the same content, the call converges to 200 without inserting anything, which is what makes a retry after a lost response safe. If it exists carrying different content, that is two different logical adds colliding on one id and you get 409 element_exists rather than a silent overwrite. Supplying your own id is therefore recommended, and it is the only way to address an element from a later operation of the same batch.

slot_id accepts the slot's id or its name. The name is how you address a page whose document has never been stored — see GET .../page_layout.

at_index is 0-based and must be a JSON integer. An index past the end of the slot appends. props is free-form, but the props that carry HTML or a URL (text, customButtonLink, and the socials URLs) must hold a string — sending an object or an array there is rejected with 422 invalid_prop_type.

Requires the write scope, modify on the board, and the page-layout API write flag on the workspace (otherwise 403 page_layout_api_disabled).

modify is checked on the board that governs the design, which is usually the board in the path but not always: one design document can be bound to more than one published page, and the oldest binding governs all of them — an edit rewrites every page it serves. A caller who may modify the board it addressed but not the governing one gets 403 board_not_authorized. A read-only board gets 403 board_read_only, and so does a read-only subboard whose section you address with subboard_token.

Parameters whose value is a string — slot_id, element_id, subboard_token and the other id arguments — may be sent either as query parameters or as keys of the JSON request body. They are listed here as query parameters because this document carries one body schema per operation; both forms reach the same handler.

Everything else must be a JSON body value. at_index, column_index, create_new_column, element, ordered_ids and operations are typed (integer, boolean, object, array) and a query-string value is always a string, so this API answers ?at_index=0 with 422 invalid_parameter rather than guessing what you meant. That is deliberate: coercing here would make this endpoint accept a payload the batch endpoint rejects.

DELETE is a further restriction: send its addressing in the query string. Request bodies on DELETE are dropped by some proxies and cannot be sent at all by some clients, so a body-addressed delete is a delete that can silently lose its target.

Freshness. This API is immediately current: a follow-up GET .../page_layout reflects this write straight away. The public page at /s/<slug> is only eventually current — the write purges the cached render, but edge points of presence can keep serving a recent copy for up to an hour. Do not tell a user to reload and expect to see the change.

Concurrency. You are not the only writer: a person can be editing the same page in the Publish editor while you write. Send the version_token from GET .../page_layout back as expected_version_token and this call becomes a compare-and-swap — if the document moved in between you get 409 stale_document and nothing is applied, so read again, redecide and re-send rather than overwriting whoever moved it. Omit it and the write simply applies. It matters most on PATCH, which replaces an element wholesale.

Did it actually do anything? A successful write can legitimately change nothing — that is what makes retries safe. converged: true means the goal state already held (a delete of an id that is not there, a move that would change no order, a re-sent identical add). materialized: true means the page had no stored document and this call saved the template design it was already rendering; it can arrive together with converged. Both keys are absent when false, never false.

Absent is not the same as false. converged is a claim four of the five verbs make and PATCH makes none: its merge is unconditional, so it never reports convergence even when it replaced an element with byte-identical content. Absent therefore means "this verb has no opinion", not "something changed". On PATCH — the verb the concurrency note above singles out — compare the version_token you sent with the one you got back if you need to know whether the row moved, and read the element back if you need to know whether the page did.

Request

Responses

element added