You never write a collection. The build generates one set per page type, one per visible label and one per visible author, and a template’s whole job is to ask for the right name. Nothing is registered, nothing is configured, and no collection you can reach was defined by hand.
There is a seam worth naming before anything else. What a label is — who creates one, what it does to access control, how a series is configured — lives at Labels and Series. What a label renders as, and which collection name carries it, is here.
The collections Leed generates
Every page-type collection is keyed pageTypeId-<id>, optionally with a :suffix. The <id> is the page type’s id, not its slug — pageTypeId-100111, not pageTypeId-blog. Get that wrong and you get undefined back with no error, so it is worth looking the id up rather than guessing it.
flowchart LR
PT["One page type"] --> BASE["pageTypeId-<id><br/>flat, generated pages included"]
PT --> PAGED["pageTypeId-<id>:paged<br/>chunked into PageData"]
PT --> UNPAGED["pageTypeId-<id>:unpaged<br/>flat, generated pages excluded"]
PT --> LBL["pageTypeId-<id>:labels<br/>one PageData set per visible label"]
PT --> AUTH["pageTypeId-<id>:authors<br/>one PageData set per visible slugged author"]
PT --> ONELBL["pageTypeId-<id>:labelId-<labelId><br/>flat, one label — what a series reads"]
ALL["allPages<br/>every page in the build"] --> MAP["renderedPageMap<br/>built as a side effect"]
| Name | Contents | Generated pages included? | Sort | Typical template |
|---|---|---|---|---|
allPages | Every item in the build with a non-empty pageId, across every page type | Yes | Published date, then URL | None — it exists to build renderedPageMap |
pageTypeId-<id> | Every page of one page type, as a flat array | Yes | Published date, then URL | A hand-rolled listing that wants no chunking |
pageTypeId-<id>:paged | The same pages chunked into PageData objects | No | Published date, then URL | The scaffolded <slug>-index.hbs card grid |
pageTypeId-<id>:unpaged | The same pages, flat | No | Published date, then URL | A noRender page type’s list template |
pageTypeId-<id>:labelId-<labelId> | Pages of that type carrying one specific label, flat | No | Published date, then URL | Series rendering, via the series helpers |
pageTypeId-<id>:labels | One PageData set per visible label | Only if the page carries the label | Published date, then URL | The scaffolded label index |
pageTypeId-<id>:authors | One PageData set per visible author that has a slug | Only if the page carries the author | Published date, then URL | The scaffolded authors index |
pageTypeIds | The ids of every page type, as strings | — | Definition order | Iterating every page type once |
aiSupportPluginCollector | Internal. Feed-eligible pages, used to generate llms.txt | No | — | None — do not paginate it |
Four of them do the real work.
:paged — a card grid
A PageData[]: the page type’s pages chunked into fixed-size groups, each group wrapped in an object that also carries its own position. Chunk size comes from your company’s paging settings — paging.size, default 10 — and paging.minimum, default 0, prevents a stranded final chunk: if the last chunk would hold fewer than the minimum, its items are folded back into the one before it rather than getting a page of their own. Both reach the build through src.11tydata.json; see Global Site Data and the Data Cascade.
:unpaged — a flat list
The same pages, unchunked, with generated pages excluded. This is what a noRender page type’s list template paginates over with alias: "item" — one output page per item, at a path the template computes. It is also what redirectIndex: first and redirectIndex: last resolve against.
:labels and :authors — two-layer sets
Both produce one PageData set per key rather than one per chunk, which is why templates that use them paginate with alias: "list" and then loop the set inside.
A label appears only if it is visible — an internal-only label generates no collection. An author appears only if they are visible and have a slug; see Authors and Contributors. One asymmetry between the two: the authors sets are built with a chunk size of Number.MAX_SAFE_INTEGER, so an author’s page never splits no matter how much they have written, while a label’s set chunks normally.
allPages, and the side effect that matters
allPages is every item in the build carrying a non-empty pageId, sorted deterministically. Building it also builds renderedPageMap — a map from page id to the path that page was written to.
That map is why a template can answer “did this page actually render in this build?”. Three helpers consult it: pageIdExists, hrefIsRenderable and menuItemShouldRender. It is what lets the CMS pre-build navigation for pages that are not published yet — a menu leaf pointing at an unrendered page has its markup dropped instead of emitting a dead link.
The PageData shape
This is the object a paginated template receives as list — or as whatever you named in alias.
| Field | Type | Meaning | 1-based? |
|---|---|---|---|
name | string | The key this set belongs to: the page type id, <pageTypeId>:<labelId>, or the label or author id for a two-layer set | — |
total | number | Items in the whole set, not this chunk | — |
size | number | Items in this chunk | — |
page.first | boolean | This is the first chunk | — |
page.number | number | This chunk’s index | No — 0-based |
page.next | boolean | Another chunk follows | — |
page.previous | boolean | Another chunk precedes | — |
page.last | boolean | This is the last chunk | — |
page.rangeStart | number | Position of this chunk’s first item within the set | Yes |
page.rangeEnd | number | Position of its last item | Yes |
pagingSize | number | The chunk size used — paging.size, default 10 | — |
minimumItems | number | paging.minimum, default 0 | — |
startingIndex | number | Offset of this set’s first chunk in the whole collection array; the pagination helpers slice pagination.hrefs with it | — |
pageCount | number | Chunks in this set | — |
pageData | array | The pages in this chunk | — |
Ordering
Every collection above arrives sorted the same way: ascending by publishedAt, tie-broken by ascending page URL. The URL tiebreak is what makes the order stable across builds when two pages share a timestamp — without it, a rebuild could reshuffle them and produce a spurious diff in every paginated output page.
Ascending means oldest first. A blog index that reads newest-first gets there by reversing at render time, which is what the shipped scaffold does.
To sort by something else, use sorted:
{{#each (sorted (collection "pageTypeId-100111") "data.title") }}
<li><a href="{{ this.url }}">{{ this.data.title }}</a></li>
{{/each}}
{{#each (sorted list.pageData "data.publishedAt" reverse=true) }}
...
{{/each}}It is locale-aware, case-insensitive and numeric, so i18n sorts among the letters rather than after Z, and Chapter 10 follows Chapter 9 instead of Chapter 1. Entries missing the field sort to the end in both directions — reversing does not float them to the top.
Rendering a series
A series is a label, and its pages come from the collection pageTypeId-<the current page's page type>:labelId-<the label>. The series helpers — Series, MultipartSeries, AnnouncementSeries — take a label id string and read exactly that name.
The consequence is the one fact a template author needs from this page: a series only ever contains pages of the current page’s page type. A label applied to both a blog post and a resource does not join them into one series; each page type gets its own. That is not a rule about series, it is a rule about collection names — the page type id is baked into the name the helper builds.
The rest of it — the verified each → with → Series calling pattern, the per-iteration block variables, and what happens when the label id does not resolve — belongs to Collection, Lookup and Series Helpers, which carries the worked snippet.
Worked example: a paginated index
The shipped index scaffold paginates the :paged collection. Its front matter is the interesting half:
{
"title": "Blog List",
"publishedAt": "list",
"modifiedAt": "list",
"pageTypeId": "100111",
"pagination": {
"data": "collections.pageTypeId-100111:paged",
"size": 1,
"alias": "list",
"addAllPagesToCollections": true,
"generatePageOnEmptyData": false,
"reverse": false
},
"pageId": "0Ab3xQ9k",
"permalink": "{{ pageType this.pageTypeId 'slug' }}{{#unless list.page.last }}/page/{{ add list.page.number 1 }}{{/unless}}/index.html"
}Four of those lines do something non-obvious:
size: 1. The collection is already chunked intoPageDataobjects, so Eleventy is paginating chunks, not pages — one chunk per output page. A larger size would put several chunks on one page.alias: "list". Names thePageDataobject inside the template. Everything on this page calledlist.assumes this line.generatePageOnEmptyData: false. A page type with no published pages produces no index file at all, rather than an empty grid.- The computed
permalink. The last chunk is written to the page type’s root (/blog/) and every other chunk to/blog/page/<n>/. Because collections sort oldest-first, the last chunk holds the newest pages — so the root URL is the newest content, and the template then reverses within the chunk and reverses the numbering so the reader sees it as page 1.
publishedAt and modifiedAt are set to the literal string "list", which matches the pagination alias. That is the trigger for the date resolution described at Layouts and Page Types — the list page ends up carrying a real date from its chunk instead of the word list.
The full shipped front matter, tokens and all
This is the scaffold as Leed commits it, before token substitution:
{
"title": "###PAGE_TYPE_NAME### List",
"publishedAt": "list",
"modifiedAt": "list",
"pageTypeId": "###PAGE_TYPE_ID###",
"pagination": {
"data": "collections.pageTypeId-###PAGE_TYPE_ID###:paged",
"size": 1,
"alias": "list",
"addAllPagesToCollections": true,
"generatePageOnEmptyData": false,
"reverse": false
},
"pageId": "###NEW_SHORT_ID###",
"permalink": "{{ pageType this.pageTypeId 'slug' }}{{#unless list.page.last }}/page/{{ add list.page.number 1 }}{{/unless}}/index.html"
}Every ###TOKEN### is replaced once, at commit time, so none of them survives into your repository. The full token list is at Page-Type Scaffolding Templates.
Rendering the control
The scaffold ships its own two-link Newer/Older control. For the full numbered control, call the drop-in component instead:
{{> leed/components/pagination }}It reads list from the page root by default, or takes data=myAlias when you paginated under a different name. It renders nothing at all when pageCount is 1 or less, so an index that fits on one page needs no {{#if}} around it.
The component’s context requirements are at Head and Component Partials, and the three helpers it is built from — PaginationRange, PaginationLinks and SmartPaginationLinks — have full signatures at Pagination Helpers. Every helper that reads a collection, including collection, sorted, filterObject, Authors and Labels, is at Collection, Lookup and Series Helpers. The index, authors and label templates that consume all of this are at Page-Type Scaffolding Templates.