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": {},
"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
- Index page (
/blog, scope*public): afunction_searchforstatus = published, sorted bypublished_atdescending, limit 20 — the list-query shape from templates. Iterate results into post cards. - Post page (
/blog/post,*public): readsslugfrom the page params, fetches withfunction_search_single_result(term onslug+ term onstatus = published— drafts 404 for the public), renders title/body with state elements. - Feed: the same list query behind an API block → JSON for external readers — see Creating a REST API.
The Publishing Flow
- An editor form (
*loggedin+ customeditorscope) writes drafts withfunction_create_document_from_mutations— the form template from Block Templates. - The slug is generated once from the title (lowercase, dashes) with a uniqueness check before create.
- Publishing is a status mutation (
draft → published+published_attimestamp) viafunction_update_document_mutations. - 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.
