Data requests
Named dataRequests on page and component root.props own list/get/create/update behavior. Buttons and InteractiveWrapper only trigger a named request. VariableInputBlock edits variables that requests read and write. List UIs repeat rows with VariableArrayTemplateBlock bound to an entry[]/array variable.
Mental model
Queries decide what they do. A request carries op, objectTypeId, args (filters, searchQuery, fieldValues — often with {{var:id}} or {{bind:route.*}} tokens), an explicit select, reverse include lists, and optional resultWrites that copy results into editor variables after a successful run. Blocks bound to requests.<name> do not need a write. Button and InteractiveWrapper only set action to runRequest or runMutation plus requestName (optional include alias, loadingText/successText/errorText). Do not put search or submit logic on the button itself.
Placement
Only content.root.props.dataRequests, content.root.props.inputSchema, and content.root.props.variables are read. dataRequests are stored per content locale. A locale that has no saved copy uses the primary locale's requests. {{bind:locale}} is that content locale code (for example en-US). {{bind:lang}} is en or tr for payment pages. The server sets both and ignores a page-supplied value. Host pages map component inputs with ComponentBlock.props.inputMappings ({inputId, source}). Top-level content.dataRequests, root.dataRequests (sibling of props), kind/operation/mappings/variables at the wrong level, and guessed shapes are stored verbatim but ignored by detailed mode and runtime — get_design_warnings flags them.
Schema
dataRequests[]: { name, op: list|get|create|update, objectTypeId, args?, select?, include?, resultWrites? }. args: entryId, pathParam, filters, sortBy, sortDirection, thenBy, thenDirection, limit, page, offset, excludeRouteEntry, searchQuery, searchFieldKeys, fieldValues. sortDir is an alias of sortDirection and thenDir is an alias of thenDirection on args, include, fallback, and resultWrites. filters[]: { fieldKey, operator, value?, valueFromField?, whenEmpty? }. A filter value may be {{var:id}}. When that variable is blank, whenEmpty no-filter (default) drops the filter and no-results returns no rows. select lists every field to return, including relation keys and dotted forward paths (category.name); each hop is a relation and the last segment is a field. A dotted entry such as order.price may use format number with an ISO currency code. {{bind:…}} prints that formatted string. A literal currency symbol next to currency repeats the symbol. A missing or empty select returns no field values. include.select follows the same rule. include[] loads reverse relations: { as, from, via, filters?, sortBy?, sortDirection?, limit?, select?, include?, rerun? }. from is the related type slug, via is its relation_object aimed at the parent, as is the result key. Nested include stops at NAMED_DATA_REQUEST_MAX_DEPTH (3). rerun include-only (default when omitted) re-fetches that include when only its variables change; request re-runs the parent. A variable used only inside an include re-fetches that include. The parent result and other includes keep the same objects, and the parent loading flag stays clear. resultWrites[]: { variableId, source?, distinct?, sortBy?, sortDirection?, orderFrom?, defaultVariableId? }. Writes are optional and only copy into variables. source defaults to results and may be total, hasMore, page, or pageCount. It also supports results[].field and include paths such as images[].size (entry fields are read from fieldValues). results.0.field copies the first row's field into a text variable. results.field does not read a field from the results array. results[].field is the projected list, not a single string. distinct keeps unique scalar values. sortBy value or a field key orders them; orderFrom is a parent field path (array or multiline) that wins, and values missing from it stay after in sort order. defaultVariableId sets that variable to the first written value when it is empty. Mapping source requests.<name>.results.<as> reads an include. inputSchema[] (components): { id, name, type: string|number|boolean|image|richText|entry|entry[] }. Tokens: {{var:id}}, {{bind:route.*}}, {{bind:query.*}}, {{bind:locale}}, {{bind:lang}}. Text fields and component inputs insert this page's query fields, variables, and URL params as chips. List rows: VariableArrayTemplateBlock.arrayBinding = {{var:hits}} (or {{var:items}}); slot uses {{var:item.<fieldKey>}}. A whole-value {{bind:requests.<name>.results}} is also accepted on VariableArrayTemplateBlock and SlotSliderBlock; each row is flattened so {{var:item.<fieldKey>}} still works. itemFilter on that block filters the bound array in the client before minItems; whenEmpty follows the same rule. An empty filtered list can render the emptyContent slot. Those variable ids are clientDependencies on detailed views, not request dependencies. Button runRequest may set include to an alias to re-fetch only that include. A select entry is a field key or { field, format?: "html"|"excerpt", maxChars?, suffix? }. A string, and format excerpt, return a plain excerpt (maxChars overrides 160, suffix defaults to …). format html leaves that field's stored HTML. format date accepts style short, medium, or long, or a pattern such as dd/MM/yyyy (pattern wins). The value is formatted in the content locale, so {{var:item.date}} shows that text. args.page is 1-based; args.offset wins when both are set and page becomes (page - 1) * limit. List results include total, hasMore, the resolved 1-based page, and pageCount (ceil(total / limit), at least 1). Bind {{bind:requests.<name>.total}}, {{bind:requests.<name>.hasMore}}, {{bind:requests.<name>.page}}, and {{bind:requests.<name>.pageCount}}. URL params stay on {{bind:query.*}}. A direct load honors ?page= on first render through {{bind:query.page}}. Filter operator overlaps matches any shared id on a multi relation_object. valueFromRoute "$id" or "id" is the route entry id. Any other valueFromRoute uses that URL param when it is present (slug on /urun/[slug]); otherwise it is relation ids from that field on the route entry. Omitting searchFieldKeys while searchQuery is set searches text, email, phone, multilineText, url, and enum fields. Filter operator contains matches text in that field. A top-level variable may set syncQueryKey to a URL query key: the public page seeds it from that key, including the server render, and writes changes back with replaceState (page 1 is omitted). When another synced variable changes, paging variables (syncQueryKey "page", or any variable referenced by a list args.page) and the raw page query key reset to 1. Number updateVariables ops add and subtract clamp to 1…pageCount. A page past pageCount clamps the request to the last page. When args.page is {{var:id}} and offset is omitted, that variable is set to the clamped page. An explicit args.offset is left as requested. args.excludeRouteEntry drops the route entry id. args.fallback { sortBy, sortDirection } fills up to limit when the filtered rows are short, skipping ids already returned and the route entry. Button actions requestNextPage and requestPrevPage name the request and a query key (default page), write the next or previous page into that URL query, and re-run the request. requestPrevPage is disabled on page 1. requestNextPage is disabled when page is the last page (page >= pageCount). Lists that use valueFromRoute or excludeRouteEntry are cached per route id and slug, and their result writes are in the server HTML for both page and component requests. InteractiveWrapper urls resolve {{var:}} and {{bind:}} the same way Button does. args.thenBy and args.thenDirection append a second sort; omit them to keep the primary sort only. Bind the page with {{bind:query.page}}. VariableInputBlock labelGap is the label-to-control gap in px; omit it to keep the stylesheet. When documentId is omitted, the sample id is a live page that still has a public route. {{bind:session.id}} is the httpOnly website session. The server sets it; a value sent from the page is ignored. {{bind:user.id}}, {{bind:user.email}}, {{bind:user.roleLabel}}, and {{bind:user.entryId}} are the signed-in website visitor. The server sets them and ignores a page-supplied user binding. When nobody is signed in, user is absent. {{bind:user.id}} is the website user id in the access token (websiteUserId). {{bind:user.entryId}} is the member object entry id (website_users.object_entry_id). They are different ids. These tokens also resolve in block text for the signed-in visitor. see_own matches entries this member created (created_by_website_user_id equals {{bind:user.id}}). Self-registration sets that column, so a list filter id = {{bind:user.entryId}} returns that member when the role has see_own on the user type. A filter does not widen that grant. id = {{bind:user.entryId}} with whenEmpty: no-results returns no rows when the bind is blank, and still returns no rows when see_own does not match the entry. A signed-in visitor is approved and not blocked. email_verified does not block sign-in. Registration assigns a role: the user-type role field default, otherwise the role labeled user, otherwise the oldest role. assign_user_role only changes that role and is not required to be signed in. {{bind:session.id}} stays the guest cookie. A field marked sessionOwned is stamped with that id on public create and is not client-writable. Anonymous list, get, update, include, and forward loads of a type with a sessionOwned field only see the entry for the current session. That does not require public view_all. view_all lists every row; without it the session filter still returns this session's rows, and a field with read false stays hidden. Anonymous responses omit sessionOwned field values. Public slug, name, and label skip those fields. A list of a session-owned type is not a public_list_hidden warning. A filter fieldKey id matches the entry id. Any other unknown fieldKey matches no rows and get_design_warnings reports it. sortBy createdAt is the created_at column, and mutatedAt is mutated_at. Anonymous create and update run the saved request for that page or page component and name. The browser may send variable values, the honeypot, and formStartedAt. A name that is not a saved create or update is rejected. Tokens in the saved args still resolve. List and get run the request stored on the page. Public field write grants are the write boundary, and list/get results drop fields the viewer cannot read, the same way object displays do. A create may include args.children: [{ objectTypeId, fieldValues, linkField }]. Children are inserted in the same transaction. linkField must be a relation to the new parent and is stamped even when visitors cannot write it. Child entry_created runs, then the parent's, so a payment initialize sees the lines. {{var:sel.0.price}} reads fieldValues when price is not a direct property; a direct property still wins. Button action.type is read as action.kind when kind is absent.
Select options (VariableInputBlock)
Select options come from the editor variable, not from block props. Use type: "list" with options: [{ id, label }, …]. Alias type: "options" is accepted as list. Option entries may use value instead of id. Pair with VariableInputBlock control: "select" (or auto).
Public create
Public create and view_all apply only while the visitor is anonymous. A signed-in visitor is checked only against their website user role. Those grants are not added to the public grants. Create requires mutate_own on that object type. A role whose object_types map lists any type and omits another type denies the omitted type, even when public create is true. An empty object_types map is the legacy allow for create, read, and update. Anonymous create/update uses field grants, a honeypot, and a cookie rate limit of 20 mutations per minute. List and get are not rate limited. Public create and update also reject a missing formStartedAt and a submit sooner than antiSpam.minFillMs milliseconds after that stamp (default 2000, allowed 0–10000). The page stamps formStartedAt when the data-request provider hydrates, so a click inside that window on a freshly loaded page fails with form.too_fast. A stamp older than 2 hours is rejected with form.spam_rejected. A millisecond stamp still in the future is also form.spam_rejected. The age is server receive time minus the stamp. A stamp taken when the request is sent does not return form.too_fast when that trip is longer than antiSpam.minFillMs. A failed run includes errorCode next to the message. Set antiSpam.minFillMs to 0 on a one-button checkout. The honeypot and the rate limit stay. Prefer create_component_preset kind=form for starters. When the linked form component mode is login, register, request_password_reset, or update_own, runMutation uses that mode. Email and password stay in fieldValues. There is no websiteUserSignIn or websiteUserSignUp button action. Sign in or sign up with a user-kind form whose formMode is login or register. formMode lives on that page component at content.root.props.formMode. Set args.formComponentId to that page component's id, then use a create or update data request, and a button action runMutation. The site creates the page path. The public create path also rejects empty required object fields on the server even if client attributes are removed. A create or update response reloads the row from the primary and returns the selected fields even when view_all is false. A field with read false is still omitted. When a mutation fails, the button that names that request shows the server error on its label (or errorText when that prop is set) and clears the loading state.
Recipes
Contact form: variables per field + VariableInputBlock + dataRequest op create with args.fieldValues from {{var:*}} + Button runMutation.
Search / list: variables q (text) and hits (entry[]) + dataRequest op list with args.searchQuery {{var:q}} and resultWrites to hits + VariableInputBlock + Button runRequest + VariableArrayTemplateBlock with arrayBinding {{var:hits}} and slot content using {{var:item.<fieldKey>}} (entry fieldValues are flattened onto item). Wrap the template (or its slot) in FlexListBlock/GridListBlock for gaps and columns.
Refresh: Button runRequest on the same list/get name. A list or get whose args mention {{var:id}} also re-runs when that variable changes. A variable used only inside an include re-fetches that include; Button runRequest include set to the alias does the same.
Size selector: an array-of-text variable (VariableInputBlock renders add/remove rows) or a list write with source images[].size or results[].size, distinct true, optional sortBy value and orderFrom the parent sizes field, and defaultVariableId set to the selection variable. Repeat set-variable buttons in a VariableArrayTemplateBlock ({{var:item}}, minItems 2 so one size hides the selector). Filter the gallery with VariableArrayTemplateBlock itemFilter on {{var:size}} (whenEmpty no-filter or no-results) and optional emptyContent. A lone matching button is aria-pressed. In a group, the selected button is aria-checked with data-selected.
Product detail (one get, as on products/[slug]): one get, forward select such as category.name, and reverse includes for sections and images. Map inputs from requests.<name>.results and requests.<name>.results.<as>.
Paging: args.page {{bind:query.page}} or {{var:pg}} with syncQueryKey, and a limit. A direct load of ?page= uses that page on first render. A page past pageCount clamps the request to the last page. When args.page is {{var:id}} and offset is omitted, that variable is set to the clamped page. Changing another synced variable resets the page to 1. Button requestNextPage and requestPrevPage (query key page) write the next or previous page and re-run. Bind {{bind:requests.<name>.total}}, .hasMore, and .page, or copy them with resultWrites source total, hasMore, or page.
Related posts: a list filter { fieldKey, operator: overlaps, valueFromRoute } on the shared relation, args.excludeRouteEntry, and args.fallback when the overlap is shorter than limit. SlotSliderBlock.arrayBinding repeats the first slide per item with {{var:item.*}}.
Introspection
get_pages / get_components mode=detailed returns requests, requestsDetailed (args, select, include with that include's own dependencies, resultWrites), inputSchema, clientDependencies for client-side list filters, and parseErrors. Parent request dependencies omit variables used only inside an include. get_design_warnings reports unknown blocks/actions, misplaced keys, and unparseable rows.
Presets and migrate
MCP save_component_preset {objectTypeId, kind: list|detail|form}. The list preset includes VariableArrayTemplateBlock bound to {{var:hits}} with a sample {{var:item.*}} heading. List rows use VariableArrayTemplateBlock and page-safe blocks.