# Pagination

Page through a long list with Paginator, so a user walks it with buttons. Covers where the items come from, what a paginator takes, and rendering each item.

A list longer than one message needs a Next button, and that button has to know which page comes next. If you keep the page number in memory, every button stops working the next time your bot restarts. If you write it into the id by hand, the bounds checks and disabled buttons are yours to write too.

A [`Paginator`](https://docs.seedcord.org/packages/gateway/latest/paginator) sends one page of a list and routes the clicks on its nav buttons. Every button carries its target page inside the custom id, so a click still works after your bot restarts.

```ts title="src/handlers/Roles.ts"
import {
    ArraySource,
    ButtonRoute,
    Paginator,
    SlashHandler,
    SlashRoute
} from '@seedcord/gateway';

export const Roles = new Paginator({
    prefix: 'roles',
    source: new ArraySource(
        (ctx) => [...(ctx.guild?.roles.cache.values() ?? [])],
        { perPage: 15 }
    ),
    renderItem: (role, index) => `${index + 1}. ${role.name}`,
    ephemeral: true
});

@ButtonRoute(Roles.cursor)
export class RolesNav extends Roles.Handler {}

@SlashRoute('roles')
export class RolesList extends SlashHandler<'roles'> {
    public async execute(): Promise<void> {
        await Roles.start(this);
    }
}
```

`prefix` names the route every nav button carries, the same way a [`CustomId`](https://docs.seedcord.org/packages/custom-id/latest/custom-id) prefix names one. A click routes by its prefix alone, so pick a name nothing else in your bot uses.

The paginator builds its own nav handler and puts it on [`Roles.Handler`](https://docs.seedcord.org/packages/gateway/latest/paginator#handler). Extend that with an empty body, hand `Roles.cursor` to [`@ButtonRoute`](https://docs.seedcord.org/packages/core/latest/button-route), and export it from wherever seedcord scans your handlers. Clicks don't route until that class exists.

[`start()`](https://docs.seedcord.org/packages/gateway/latest/paginator#start) sends the first page through your handler's reply surface, which picks reply, edit, or follow-up from the interaction's current state.

The first page is page 0, so `Roles.start(this, 3)` opens the fourth page. If the number is higher than the last page, `ArraySource` opens the last page.

> **Gateway and http differ**
>
> `start()` resolves to a discord.js `Message` on gateway and an `APIMessage` on http.

[`page()`](https://docs.seedcord.org/packages/gateway/latest/paginator#page) builds the same message and hands it back without sending. Its page number is required, as in `Roles.page(this, 0)`. You can pass the result to `this.reply()` or `this.followUp()` as is, since seedcord's reply methods add the Components V2 flag. If you send it another way, like discord.js's `channel.send()` or your own REST call, add `flags: MessageFlags.IsComponentsV2`.

## Where the items come from

[`ArraySource`](https://docs.seedcord.org/packages/gateway/latest/array-source) takes a loader and a page size. Every click runs the loader again, then slices out the page its button targets. It loads everything, so the page count is exact. That gives the nav row a working **Last** button, and the indicator reads `Page 2 of 4`. The cost is a full read of the list on every click, which [Sources](/components/pagination-sources) avoids by fetching one page at a time.

Your loader can return a promise. If it calls a database or an API, cache the result inside it.

`perPage` defaults to 10.

> **Gateway and http differ**
>
> The loader receives a [`PageContext`](https://docs.seedcord.org/packages/gateway/latest/page-context), which carries `guild: Guild | null` on gateway and `guildId: string | null` on http. Reading `ctx.guild` fails to compile there. Fetch the guild through `ctx.core.rest` where your loader reads guild data.

## What a paginator takes

{/* prettier-ignore-start */}

| key          | what it does                                                                           |
| ------------ | -------------------------------------------------------------------------------------- |
| `prefix`     | the route prefix on every nav button                                                   |
| `source`     | where one page of items comes from                                                     |
| `renderItem` | turns one item into a line, `String(item)` when left out, ignored once `render` is set |
| `render`     | replaces the whole page, covered in [Custom rendering](/components/pagination-render)  |
| `ephemeral`  | whether the first page is ephemeral, `false` by default                                |

{/* prettier-ignore-end */}

Every nav click edits the message `start()` sent. One `ephemeral` covers the whole run.

## Rendering each item

Without `render`, seedcord puts the items and a row of nav buttons in one container.

`renderItem` receives an item and its position, counted from the start of the list. The index counts from zero, so at a `perPage` of 15 the first item on page two gets `15`. The `Roles` sample adds one to print it as `16`.

Return a string from `renderItem` for a plain line. For an item that carries a thumbnail or a button of its own, return a [`BuilderComponent<'section'>`](https://docs.seedcord.org/packages/core/latest/builder-component).

```ts
import {
    ArraySource,
    BuilderComponent,
    Paginator
} from '@seedcord/gateway';

export const Reports = new Paginator({
    prefix: 'reports',
    source: new ArraySource(() => loadReports()),
    renderItem: (report, index) => new ReportRow(report, index + 1)
});
```

Return `ReportRow` whole, since seedcord reads `.component` from it.

One page can mix both forms. Each `ReportRow` breaks the run of text lines and renders as its own block, in the same order as your items.

Three more pages continue from here. Each one hands you a job seedcord was doing.

* [Sources](/components/pagination-sources) pages a list too large to load whole, and writes a source of its own.
* [Custom rendering](/components/pagination-render) replaces the message, nav buttons included.
* [Paging it yourself](/components/pagination-headless) drops the paginator and keeps the page math.
