Collections

Collections are named, deterministic, locale-local ordered sets of rendered pages. They provide the shared input for article listings, pagination, taxonomies, and Atom feeds. A template can read a resolved collection, but can never create pages or alter its membership. If collections are new to you, build the tutorial's collection step first.

Quick start

A collection has three parts: define its ordering once, add pages to it, then read it from a template. This example lists English blog posts newest first.

First, define the collection in heine.toml:

[collections.posts]
order = "published-desc"

Then add a rendered page to it:

# content/en/blog/first.page
template = "post.tera"
title = "First post"
published = 2026-08-14T09:30:00
collections = ["posts"]

Finally, let a listing template read the collection:

{% set posts = collection(name="posts") %}
<p>{{ posts.count }} posts</p>

{% for id in posts.page_ids %}
  {% set post = page(id=id) %}
  <article>
    <h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
    <p>{{ post.summary }}</p>
  </article>
{% endfor %}

page_ids is the already ordered list of logical page IDs. page(id=id) looks up each member's render-facing data and creates its checked URL. This keeps one authoritative page representation without copying the whole site into every template render.

For a directory of posts, put collections = ["posts"] in its _directory.toml under [pages] instead of repeating it on every page. Give the directory's own index.page collections = [] when it should not list itself. The Membership section describes that inheritance rule in full.

Collections establish an ordered relationship only. The listing above is an ordinary authored page; collections do not independently generate output.

Terms

TermMeaning
collection definitionA site-wide named ordering policy.
membershipA page's inclusion in one or more named collections.
resolved collectionThe ordered page IDs for one collection in one locale.

A definition is global because its meaning is shared by every locale. A resolved collection is locale-local because pages, their metadata, and their routes belong to one locale tree.

Authored configuration

heine.toml defines the available collections:

[collections.posts]
order = "published-desc"

[collections.docs]
order = "weight-asc"

Collection names are simple identifiers, consistent with other authored names that become template keys. Every definition has exactly one order value:

ValuePrimary ordering
published-ascpublication time, oldest first
published-descpublication time, newest first
title-ascpage title, locale ascending
title-descpage title, locale descending
weight-ascpage weight, lowest first
weight-descpage weight, highest first
id-asclogical page ID, ascending
id-desclogical page ID, descending

Logical page ID is the final deterministic tie-breaker. A collection ordered by publication time or title requires every member to have that value. Title ordering uses the resolved collection locale's ICU collation, with literal title spelling as an additional deterministic tie-breaker. A weight ordering uses the page's already-resolved numeric weight; a member with weight = false is an error. Unknown collection fields, duplicate names, and unknown ordering values are errors at their authored locations.

Publication ordering compares the resolved instants from required local published timestamps. The site-wide editorial time zone makes this ordering deterministic without a machine-local default.

Membership

A page joins a collection through its .page metadata:

# content/en/blog/2026-08-14.page
template = "post.tera"
title = "First post"
published = 2026-08-14T09:30:00
collections = ["posts"]

A directory descriptor can provide the normal recursive default:

# content/en/blog/_directory.toml
[pages]
collections = ["posts"]

Page-level collections replaces the inherited default. An explicit empty array opts out, which is useful for a directory index that should share its directory's template policy but not appear in its article listing.

Each listed name must refer to a configured collection, and a page may name a collection only once. Only rendered pages may be members: a page with no template, or with template = false, cannot be listed. Ordering diagnostics show the relevant membership, publication time, title, or weight value when one exists, and the configured ordering declaration.

This is a concise extension of page metadata, not a second content-discovery mechanism.

Template contract

Use collection() to read a current-locale collection:

{% set posts = collection(name="posts") %}
<p>{{ posts.count }} posts</p>

{% for id in posts.page_ids %}
  {% set post = page(id=id) %}
  <article>
    <h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
    <p>{{ post.summary }}</p>
  </article>
{% endfor %}

Each collection view exposes:

FieldMeaning
nameThe configured collection name.
countNumber of member pages in this locale.
page_idsThe ordered logical IDs of member pages.

Use each ID with page() to obtain the page's render-facing data and checked URL. This keeps the collection compact while the page remains authoritative for its own metadata and content.

Every configured collection is present for every locale, including when it has no members. After {% set posts = collection(name="posts") %}, a template can therefore rely on posts.count being zero rather than needing to distinguish an empty collection from a missing definition.

Collection neighbors

Use collection_neighbors() from a member page when its previous and next links should follow the collection's configured order rather than directory weight order:

{% set neighbors = collection_neighbors(name="posts") %}
<nav aria-label="Post navigation">
  {% if neighbors.before %}
    <a href="{{ neighbors.before.relative_url }}">Previous</a>
  {% endif %}
  {% if neighbors.after %}
    <a href="{{ neighbors.after.relative_url }}">Next</a>
  {% endif %}
</nav>

The result always has before and after. before is the preceding member and after the following member in the resolved collection order. Each is a complete page view or null at the corresponding end. The function uses the current locale and current authored page. It fails at the template call if the named collection is unknown or the page is not a member. It is therefore a different relationship from siblings() and sibling_neighbors(), which use pages from one source directory ordered by numeric weight.

Locale behavior

Definitions are shared, but membership and resolution happen independently for every configured locale. After resolving posts, its count may therefore differ between English and German, and an empty collection is valid. Translation IDs only connect known counterpart pages; they do not put pages into collections or require matching membership across locales.

This lets a locale publish a deliberately smaller or differently organized body of content without hidden fallback or invented translations.

Pagination

Pagination is the first generated-output consumer of a resolved collection, not an alternative collection mechanism. It attaches to an ordinary authored listing page, which supplies page-one metadata, template, and canonical route. Later pagers are generated render tasks without authored page identities. The pagination guide documents its syntax, routes, template contract, navigation policy, diagnostics, and quick-build behavior.

Taxonomies

Taxonomies classify the members of a collection into literal terms and generate taxonomy-index and term-listing pages. They preserve the collection's member order; the taxonomy guide explains term routes and template values.