Listings

Anywhere you want the list, add a listing for the same collection. Inside it, sub-commands do the work: operations (filter, filter-any, sort, group, limit) and templates (item, plus optional separator, heading, start, end and empty). The operations run in the order you write them:

<!-- localhoster:listing collection="posts" -->
  <!-- localhoster:listing-filter field="status" op="eq" value="published" -->
  <!-- localhoster:listing-filter-any -->
    <!-- localhoster:listing-filter field="category" op="eq" value="news" -->
    <!-- localhoster:listing-filter field="category" op="eq" value="updates" -->
  <!-- localhoster:/listing-filter-any -->
  <!-- localhoster:listing-group by="category" using="date" order="desc" -->
  <!-- localhoster:listing-sort field="category" order="asc" -->
  <!-- localhoster:listing-limit start="0" count="20" -->
  <!-- localhoster:listing-start
       ```
       <ul class="posts">
       ``` -->
  <!-- localhoster:listing-heading
       ```
       <h3>{{category}}</h3>
       ``` -->
  <!-- localhoster:listing-item
       ```
       <li><a href="{{url}}">{{title}}</a></li>
       ``` -->
  <!-- localhoster:listing-end
       ```
       </ul>
       ``` -->
  <!-- localhoster:listing-empty
       ```
       <p class="muted">No posts yet.</p>
       ``` -->
   … localhoster writes the generated list here …
<!-- localhoster:/listing collection="posts" -->

Start & End

listing-start and listing-end bracket the list: rendered once each, before the first item and after the last, but only when the list is not empty. Both are optional:

<!-- localhoster:listing collection="blog" -->
  <!-- localhoster:listing-start
       ```
       <ul class="posts">
       ``` -->
  <!-- localhoster:listing-item
       ```
       <li><a href="{{url}}">{{title}}</a></li>
       ``` -->
  <!-- localhoster:listing-end
       ```
       </ul>
       ``` -->
   … localhoster writes the generated list here …
<!-- localhoster:/listing collection="blog" -->

Empty

listing-empty (optional) is rendered instead of the whole list when nothing matches, so a filtered listing shows a friendly placeholder rather than blank space:

<!-- localhoster:listing collection="blog" -->
  <!-- localhoster:listing-filter field="status" op="eq" value="published" -->
  <!-- localhoster:listing-item
       ```
       <li><a href="{{url}}">{{title}}</a></li>
       ``` -->
  <!-- localhoster:listing-empty
       ```
       <p class="muted">No posts yet.</p>
       ``` -->
   … localhoster writes the generated list here …
<!-- localhoster:/listing collection="blog" -->

Items

listing-item (required) is the markup rendered once per record:

<!-- localhoster:listing collection="blog" -->
  <!-- localhoster:listing-item
       ```
       <article class="card">
         <h3><a href="{{url}}">{{title}}</a></h3>
         <p class="muted">{{date}} · {{tags}}</p>
         {{summary}}
       </article>
       ``` -->
   … localhoster writes the generated list here …
<!-- localhoster:/listing collection="blog" -->
PlaceholderDescription
{{field}}any field the record declares (e.g. title, date, tags)
{{url}}the record page’s own path

Separators

listing-separator (optional) is inserted between items, never before the first or after the last. A listing uses either a separator or a heading, not both:

<!-- localhoster:listing collection="blog" -->
  <!-- localhoster:listing-item
       ```
       <li><a href="{{url}}">{{title}}</a></li>
       ``` -->
  <!-- localhoster:listing-separator
       ```
       <hr>
       ``` -->
   … localhoster writes the generated list here …
<!-- localhoster:/listing collection="blog" -->

Headings

listing-heading (optional) is rendered per item but shown only when it changes from the item before it, so a sorted list subdivides into headed groups; put any rule or spacing into the heading markup itself:

<!-- localhoster:listing collection="posts" -->
  <!-- localhoster:listing-sort field="category" -->
  <!-- localhoster:listing-heading
       ```
       <h3>{{category}}</h3>
       ``` -->
  <!-- localhoster:listing-item
       ```
       <li><a href="{{url}}">{{title}}</a></li>
       ``` -->
   … localhoster writes the generated list here …
<!-- localhoster:/listing collection="posts" -->
PlaceholderDescription
{{field}}any field the record declares; the heading shows whenever this value changes
{{url}}the record page’s own path

Filtering

listing-filter keeps records where field compares to value by op. Repeating it requires all of the conditions; for any-of (OR) instead, group filters in a filter-any (below):

<!-- localhoster:listing collection="guides" -->
  <!-- localhoster:listing-filter field="topic" op="contains" value="caddy" -->
  <!-- localhoster:listing-filter field="date" op="gte" value="2026-01-01" -->
  <!-- localhoster:listing-item
       ```
       <li><a href="{{url}}">{{title}}</a></li>
       ``` -->
   … localhoster writes the generated list here …
<!-- localhoster:/listing collection="guides" -->
ParameterDescription
fieldthe record field to test
ophow to compare the field to the value
OperatorMatches whenExample
eqthe field equals the valueop="eq" value="published"
nethe field differs from the valueop="ne" value="draft"
ltless than (numbers, ISO dates)op="lt" value="2026-01-01"
lteless than or equalop="lte" value="10"
gtgreater thanop="gt" value="0"
gtegreater than or equalop="gte" value="2026-01-01"
containsthe value appears anywhere in the fieldop="contains" value="caddy"
startsthe field starts with the valueop="starts" value="How"
endsthe field ends with the valueop="ends" value=".md"
valuethe value to compare against

Filter Any

listing-filter-any is a paired block holding two or more plain listing-filters; a record passes if it matches any of them (OR). It sits anywhere in the pipeline, like a filter, and may contain only listing-filter commands:

<!-- localhoster:listing collection="posts" -->
  <!-- localhoster:listing-filter-any -->
    <!-- localhoster:listing-filter field="category" op="eq" value="news" -->
    <!-- localhoster:listing-filter field="category" op="eq" value="updates" -->
  <!-- localhoster:/listing-filter-any -->
  <!-- localhoster:listing-item
       ```
       <li><a href="{{url}}">{{title}}</a> ({{category}})</li>
       ``` -->
   … localhoster writes the generated list here …
<!-- localhoster:/listing collection="posts" -->

Sorting

listing-sort orders the set by a field:

<!-- localhoster:listing collection="blog" -->
  <!-- localhoster:listing-sort field="date" order="desc" -->
  <!-- localhoster:listing-item
       ```
       <li><a href="{{url}}">{{title}}</a></li>
       ``` -->
   … localhoster writes the generated list here …
<!-- localhoster:/listing collection="blog" -->
ParameterDescription
fieldthe field to order by
orderthe direction
ValueMeans
ascascending: lowest first (oldest dates, A first)
descdescending: highest first (newest dates, Z first)

Grouping

listing-group groups records by by, sorts each group by using/order, then takes a slice with start and count (default 0 and 1, so one representative per group). Raise count for the top N from each group, or set start to pick a position, like the second; a group may yield fewer:

<!-- localhoster:listing collection="posts" -->
  <!-- localhoster:listing-group by="category" using="date" order="desc" count="3" -->
  <!-- localhoster:listing-item
       ```
       <li><a href="{{url}}">{{title}}</a> ({{category}})</li>
       ``` -->
   … localhoster writes the generated list here …
<!-- localhoster:/listing collection="posts" -->
ParameterDescription
bythe field to group on
usingthe field that sorts within each group
orderthe sort direction within the group
ValueMeans
ascsort each group ascending (lowest/earliest first)
descsort each group descending (highest/latest first)
starthow many to skip within each group (default 0)
counthow many to keep from each group — a maximum (default 1)

Limiting

listing-limit takes a slice with start and count. It can sit anywhere in the pipeline, so you can trim before or after a group, or page through results:

<!-- localhoster:listing collection="blog" -->
  <!-- localhoster:listing-sort field="date" order="desc" -->
  <!-- localhoster:listing-limit start="0" count="5" -->
  <!-- localhoster:listing-item
       ```
       <li><a href="{{url}}">{{title}}</a></li>
       ``` -->
   … localhoster writes the generated list here …
<!-- localhoster:/listing collection="blog" -->
ParameterDescription
starthow many records to skip (the offset)
counthow many records to render

Errors

A listing is validated before its list is generated; a problem is reported against the page and the listing, and the site is left unchanged until it is fixed (other sites still run):

MessageWhen it appears
listing "…": a listing needs a listing-item templateno listing-item (the one required template)
listing "…" may not contain …an unrecognised sub-command inside the listing
listing "…": listing-filter needs field="…" (also op, value)a filter, sort or group missing a required attribute
listing "…": listing-filter op="…" is not one of …an unknown comparison operator
listing "…": listing-sort order must be asc or desca sort or group with an order other than asc/desc
listing "…": listing-limit needs start and/or counta listing-limit with neither bound
listing "…": listing-filter-any needs at least one listing-filteran empty filter-any block
listing "…": listing-filter-any may only contain listing-filter (found …)a filter-any holding anything but plain filters

Next steps