Browse the docs ▾

Tutorials

Build a blog or publishing site

Posts, an index, tag pages and related posts, all from one content type, and a routine the owner can keep using.

The brief: "A site for our food blog, with recipes grouped by tag."
You'll end with /blog listing posts in date order, /blog/<slug> for each post, an address for each tag, and related posts under each one.

1. Create the post type

Send add_item_page {name: "Blog posts", url: "/blog"}. A page named Blog in a starter design may show sample cards, so point its list at the new type rather than building a second index. Call list_content_types for the new type's id.

2. Add a date field

A list's newest sort is the order items were made, not when they were written. For a blog with real dates, add a field of type string labelled Published on with preview_content_fields, and sort by published_on:desc. Values like 2026-03-16 or 16 March 2026 compare as dates.

3. Design the post page

Read the outline of the new page. It starts with the title, cover and body. Style a comfortable reading width, around 680px, with maxWidth and auto margins. Bind store.published_on for the date, and bind the tag with listData.primary_tag.name in lists.

4. Write the posts

create_item takes Markdown in body, or HTML with body_format: "html". Outside images in the body are copied into the business's media. Send an idempotency_key every time, so a retry doesn't create a second post. Use the first tag as the main one, since "similar posts" reads it. Keep each body under 60 KB as stored.
Write from what the owner gave you. Don't invent recipes, statistics, customer stories or quotes. If they gave you notes, ask about gaps rather than guessing.

5. Build the index and home row

set_data_source {page, name: "posts", content_type_id, size: 12, sort: "published_on:desc"}, then add_element kind: "list", source: "posts.list". Bind the cards. To keep cards level when summaries differ in length, use line clamping on the text: display: "-webkit-box", WebkitBoxOrient: "vertical", WebkitLineClamp: "2", overflow: "hidden". Add Next and Previous buttons with api.fetch.target.

6. Give tags their own pages

One tag page serves every tag:

1

Send add_tag_page {name: "Recipe tags", copy_of: "<index page key>"}, and bind its heading to content_tag.name.

2

Preview and publish the site after the owner's yes. A tag page opens only once it's published.

3

Use preview_tag_pages and save_tag_pages to give tags addresses, such as /snacks, and optionally a description and search text. These are live at once.

4

Call get_page_screenshot with tag: "Snacks" to check.

7. Add related posts

On the post page, a data source with same_tag_as_item: true, exclude_current_item: true and size: 3, then a list. It's worth using hide_when_empty so a post with no relatives shows no empty heading.

8. Tell search engines what these are

With preview_page_settings on the post page, set seo.reads_as to Article. Every post is then described as an article, with its own title, summary, cover and dates. Write a useful summary on each item; it's limited to 255 characters.

The ongoing routine

For each new post: create_item, show the owner the draft, preview_item_publish, then publish_item after their yes. Remember that update_item on a published post changes the live page at once, so show them the change first. It returns the previous values, so you can put them back.

A new post

{
  "name": "create_item",
  "arguments": {
    "org_id": 1042,
    "content_type_id": 402,
    "title": "Quick poha for busy mornings",
    "summary": "Ready in fifteen minutes.",
    "body": "Rinse the poha …",
    "tags": ["Snacks", "Breakfast"],
    "cover_url": "https://example.com/p.jpg",
    "fields": {
      "published_on": "2026-03-16"
    },
    "idempotency_key": "quick-poha"
  }
}

Publish it

preview_item_publish
  { "org_id": 1042, "item_id": 7812 }
→ its address and a publish_token

(show the owner, wait for yes)

publish_item
  { "org_id": 1042,
    "publish_token": "pub_…" }

Tag addresses

{
  "name": "preview_tag_pages",
  "arguments": {
    "org_id": 1042,
    "page": "recipe_tags",
    "tags": [
      { "tag": "Snacks",
        "url": "/snacks",
        "description": "Small bites." }
    ]
  }
}