Example: Simple Blog

A complete working blog: post model, list and detail pages, publishing flow, and the APIs behind them.

A blog is the smallest complete app: one storage, two pages, one publishing flow. This example shows every piece working together: clone the structure for any content-driven site.

The Post Document

Post Document
{
  "_meta": {
    "cms": 1782080295147,
    "created": 1782080295,
    "entity": 1,
    "expiry": -1,
    "guid": "a23575f49d0af385314c1f02280163374297e018a692e5e4ab85eb307ebf6ebc",
    "removed": 0,
    "ums": 1782166695813,
    "update_hash": "60e27209fa93063b5605e41605c2722ed428cae0",
    "updated": 1782166695
  },
  "author_guid": "a23575f49d0af385314c1f02280163374297e018a692e5e4ab85eb307ebf6ebc",
  "author_name": "sami b",
  "body": "# Why we rebuilt\n\nEverything starts with...",
  "published_at": 1782166695,
  "slug": "why-we-rebuilt-the-docs",
  "status": "published",
  "tags": [
    "docs",
    "meta"
  ],
  "title": "why we rebuilt the docs"
}

One posts storage, everything embedded: the author is denormalized (author_name stored next to author_guid) so list pages never join. The core storage principle from Storages.

Public Pages

  1. Index page (/blog, scope *public): a function_search for status = published, sorted by published_at descending, limit 20. The list-query shape from templates. Iterate results into post cards.
  2. Post page (/blog/post, *public): reads slug from the page params, fetches with function_search_single_result (term on slug + term on status = published: drafts 404 for the public), renders title/body with state elements.
  3. Feed: the same list query behind an API block → JSON for external readers. See Creating a REST API.

The Publishing Flow

  1. An editor form (*loggedin + custom editor scope) writes drafts with function_create_document_from_mutations. The form template from Block Templates.
  2. The slug is generated once from the title (lowercase, dashes) with a uniqueness check before create.
  3. Publishing is a status mutation (draft → published + published_at timestamp) via function_update_document_mutations.
  4. Edits create revisions automatically; the list only ever shows published.

Search

Add a search box feeding query_bool_simple_query_string_field on title + body into the same list query: lowercase the fields at write time, per search optimization.

Takeaways to Reuse

  • One storage per content type; embed what lists display.
  • Status fields drive visibility. Never delete content, transition it.
  • Public reads and editor writes are separate pages with separate scopes.
  • The same query serves the page, the API, and the search box.

Next Steps

First CRUD App The tutorial this example builds on. Storage Examples More production-shaped data models. E-Commerce Catalog The same pattern at product scale.