Browse the docs ▾

Agent guides

Use structured content and data bindings

Keep content in one place and let the design read it. Then one new item appears everywhere it should, with no design work.

Where should this content live?

Words for one page

Fixed text on the page. Use set_text.

A short list on one page

Rows written on the page. Add the list with add_element kind: "list" and source: "written", then set_list_items.

Text shared across pages

A Library item. create_library_item makes one, and a design shows it with store.<key> on any page, in the header or in the footer. preview_library_change changes it, live at once.

Anything with many entries and its own pages

Items of a content type, like posts, tours, case studies or team members.

A product

The catalogue, not content. Products have their own pages.

The parts

Content type

A kind of item, with one page design. add_item_page creates one, and list_content_types lists them.

Fields

A type's extra data. preview_content_fields, then save_content_fields.

Custom type

A structured value, such as an itinerary day. A field whose type is a custom type's id holds it. See list_custom_types.

Item

One entry. Status is draft, published or unpublished, and only published items appear on the site.

Data source

On a page, fetches items when it opens. set_data_source.

List

Repeats a card for each row. add_element kind: "list".

Binding

Makes a node show data. bind_field.

What to bind

On a page for each item

content.title, content.body, content.cover_img_url, store.<field key>, store.<field key>.<key>. A video's cover is bound with field: poster, and the page's sharing image can be {{item.cover}}.

In a list's card

listData.title, listData.summary, listData.cover_img_url, listData.url, listData.primary_tag.name, listData.<field key>.

On a tag page

content_tag.name, content_tag.description, content_tag.url.

Anywhere

A Library item: store.<key>, or store.<key>.<field> for one of the business's own types. A Library list fills a list's cards with source: "store.<key>.contents".

On the product page

listing.title, listing.price, listing.mrp, listing.description, and a category field's path from get_category_fields.

Formats

End a path with |currency, |date or |uppercase.

Counts

tours.total shows how many items the data source tours matches, so a source for one tag gives that tag's count. tours.list|count counts the rows it fetched, and tours.list|sum|minutes adds up a field over them.
A bound text refuses set_text, as it shows data. Change the data or the binding. For words around a value, use tokens in set_text: per person.

Data sources

set_data_source takes page, name, content_type_id, and optionally size (1 to 50, default 12), sort, tags, same_tag_as_item, exclude_current_item and same_tag_as_page.

Names are site-wide

A source's name can be used once across the site. Another page needs its own.

newest

Means the order items were made, not any date on them.

Sort by a field

published_on:desc. Dates like 2023-07-16 or 16 July 2023 compare as days, and numbers as numbers.

Paging

Next and Previous buttons use set_handlers with api.fetch.target.

Empty lists

set_settings hide_when_empty: true.

Conditions on data

set_display_condition {field: "<path>"} shows a node only while that value is filled, so a "Duration" row can disappear for items without one. Add op and value to compare it. For a live search, add a text box with add_element kind: "input" and give the card {field: "listData.title", op: "icontains", state: "q"}. On a text, {none_match: {source: "tours.list", fields: ["title"], state: "q"}} shows a "no results" line. A card can also take its row's colour: set_style backgroundColor "{listData.accent}".

The order of work

Fields must exist before you bind to them, and lists show only published items, so work in this order: type, fields, items, publish, design, check. The CMS tutorial has a worked example, and ops_guide from get_element_catalogue has the exact steps.
A field's value is sent whole. To change one key in a custom-type value, send every key it should keep. If a read returns a custom-type field as null, don't send that null back, as it would empty the field. Ask the owner to check it in the dashboard.

A card bound to data

{ "op": "add_element",
  "kind": "list",
  "to_ref": "all_tours/list-section",
  "position": "last",
  "source": "tours.list",
  "name": "Tour cards" }
→ "card_ref": "all_tours/list-section/…"

{ "op": "add_element",
  "kind": "heading", "level": 3,
  "to_ref": "<card_ref>",
  "position": "last",
  "text": "Tour name" }
→ "new_ref": "<heading ref>"

{ "op": "bind_field",
  "ref": "<heading ref>",
  "path": "listData.title" }

Show a row only when filled

{ "op": "set_display_condition",
  "ref": "tours/item/duration-row",
  "condition": {
    "field": "store.duration" } }