Collections

Use a collection when several parts of a site need the same ordered pages: a blog listing, pagination, a taxonomy, or an Atom feed. A collection names the pages once, then templates and generated resources can read that result without recreating the membership or order. 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. It lets a directory index share its directory's template policy without appearing 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 collection in the current locale:

{% 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.

Pass an exact configured locale when a template needs another locale's collection. Heine does not infer a translation relationship from collection membership:

{% set german_posts = collection(name="posts", locale="de") %}
<p>{{ german_posts.count }} Beiträge</p>

Use collections() when a template needs every configured collection for one locale. The array is ordered by collection name and includes empty collections:

{% for collection in collections() %}
  <a href="#{{ collection.name }}">{{ collection.name }}</a>
  <span>{{ collection.count }}</span>
{% endfor %}

collections(locale="de") selects an exact configured locale. An unknown collection name or locale fails at the template call site. collections() returns an empty array when the site configures no collections.

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 smaller or differently organized body of content without hidden fallback or invented translations.

Pagination

Pagination is the first way to create pages from 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.