You probably want the partial.
The three helpers below are what that partial is made of. Reach for them when the markup has to be yours — a different control shape, a different set of class hooks, an arrangement the partial does not produce. Everything on this page assumes the mechanics in How Helpers Work; the Helper Index (A–Z) is the way back out.
The context all three require
Each helper reads two things off the root of the render context: pagination, which Eleventy builds from the template’s front matter, and list, the alias the paginated data is bound to. Both must be present, which in practice means a template rendering one of the :paged collections. There is no way to call these helpers from an ordinary page.
When either is missing, the three do not behave the same way:
PaginationLinkslogsPagination links only works inside a pagination context. First parameter must be {reverse:boolean}.and returns an empty string.PaginationRangelogs the same sentence withPagination rangein front, and returns an empty string.SmartPaginationLinksreturns an empty string with no log line at all.
The same check is what catches an omitted first argument. None of the three shifts its parameters, so leaving the boolean off puts the trailing options object in its slot and leaves the helper with no options to read — which lands it in exactly the branch above. The boolean is genuinely required, not merely conventional, and omitting it fails the same way a missing context does.
What list holds
list is a PageData object — the chunk of items for this page, plus everything a template needs to describe where it sits in the set. Its fifteen fields are enumerated once at Collections and Pagination Data, along with the pagingSize and minimumItems settings that decide how the chunks are cut.
Two of those fields matter here. page.url is what SmartPaginationLinks compares against to find the current page, and pageCount is what both the windowing rule and the partial’s self-hiding key off.
PaginationLinks — your own markup for every page number
A block helper. It runs its body once per page in the set, and hands you two data-frame variables each time.
{{#PaginationLinks true}}
{{#eq @pageUrl page.url}}
<a aria-current="page" class="pagination-active">{{ @pageIndex }}</a>
{{else}}
<a href="{{ @pageUrl }}" class="pagination-inactive">{{ @pageIndex }}</a>
{{/eq}}
{{/PaginationLinks}}That is the shipped pattern, and the {{#eq @pageUrl page.url}} comparison is the whole of it: the helper does not tell you which iteration is the current page, so you work it out by comparing the URL it gave you with the page being rendered. eq comes from the bundled handlebars-helpers library.
The single argument reverses the href order. Pass true for a reverse-chronological archive — the newest items are on the first page, so page 1 should be the last href — and false when the natural order is the reading order.
Under the hood the helper slices the set of hrefs Eleventy generated down to the pages belonging to this key, using list.startingIndex and list.pageCount. That matters only for the two-layer collections, where one paginated template generates pages for many labels or authors at once and the global href list therefore spans all of them.
SmartPaginationLinks — ready-made numbered links
An inline helper that returns the whole number row as a string. It returns raw HTML rather than a marked-safe string, so it needs triple braces:
{{{ SmartPaginationLinks true }}}Double braces produce a page displaying its own markup as visible, escaped text — the classic symptom, and one you will spot instantly once you have seen it.
The windowing rule is exact and worth stating in full: the row always contains the first two and last two page numbers, plus the current page and its immediate neighbors, with an ellipsis wherever the sequence skips. When pageCount is 10 or fewer, every page is shown and no ellipsis is emitted at all — the rule switches off entirely rather than degrading.
Three markup shapes come out, and they are the class hooks you style:
<a href="/blog/page/5/" class="pagination-inactive">5</a>
<a aria-current="page" class="pagination-active">6</a>
<a class="pagination-ellipsis"><i class="ellipsis"></i></a>The ellipsis is an anchor with no href, so it is inert and unfocusable; the current page is an anchor with no href too, marked with aria-current="page" rather than being linked to itself. The pagination-active and pagination-inactive classes are yours to restyle — how to override Leed’s defaults without reaching for !important is at Cascade Layers and Overriding Leed.
PaginationRange — “showing X to Y of Z”
A block helper that computes the range this page covers and sets three data-frame variables for the body to print:
{{#PaginationRange true}}
<p class="text-sm">
Showing <span class="font-semibold">{{ @rangeStart }}</span>
to <span class="font-semibold">{{ @rangeEnd }}</span>
of <span class="font-semibold">{{ @total }}</span>
results
</p>
{{/PaginationRange}}The argument does more work here than it does elsewhere. With true, the endpoints are mirrored against the total and then swapped, so a reverse-ordered archive counts down the way a reader expects: on a 118-item set with ten per page, page 1 reads showing 109 to 118 of 118 rather than 1 to 10. With false the raw range is printed as stored. Whichever you choose, use the same value on all three helpers on the page — a range line and a number row that disagree about direction is a bug that survives review because each half looks correct alone.
Reference
| Helper | Form | Parameter | Sets on the data frame | Returns | Braces | On missing context |
|---|---|---|---|---|---|---|
PaginationLinks | block | reverse boolean, required | @pageIndex, @pageUrl — reset on every iteration | the concatenated block renders | {{#…}} block, no triple braces | logs Pagination links only works inside a pagination context… and returns "" |
SmartPaginationLinks | inline | reverse boolean, required | — | a raw HTML string, not marked safe | {{{ }}} required | returns "" silently |
PaginationRange | block | reverse boolean, required | @rangeStart, @rangeEnd, @total — set once | the block render | {{#…}} block, no triple braces | logs Pagination range only works inside a pagination context… and returns "" |
Data-frame variables these helpers set
| Variable | Set by | Available inside | Value | Indexing |
|---|---|---|---|---|
@pageIndex | PaginationLinks | its own block, per iteration | the page’s display number | 1-based |
@pageUrl | PaginationLinks | its own block, per iteration | the href of that page | — |
@rangeStart | PaginationRange | its own block | first item shown on this page | 1-based |
@rangeEnd | PaginationRange | its own block | last item shown on this page | 1-based |
@total | PaginationRange | its own block | items across the whole set | — |
These five are the only variables these helpers create. Everything else a pagination template reads — list.page.next, list.page.previous, list.pageCount, pagination.href.next and the rest — belongs to the data shape documented at Collections and Pagination Data, and whether a page type produces list pages at all is a page-type setting you make at Configuring a Page Type. The collections these templates iterate, and the helpers that fetch and sort them, are at Collection, Lookup and Series Helpers.