Skip to main content

Pagination

FlowCord provides two distinct pagination modes. Button pagination pages through buttons when a menu has more than will fit in Discord's component limits. List pagination pages through data — a list of items fetched from a database or cache. They serve different purposes and are configured differently.

Button pagination

Auto-activation

Button pagination activates automatically when the total number of buttons passed to .setButtons() would exceed what fits in the available action rows, after accounting for any reserved row (Back/Cancel buttons) and select menu row. When auto-pagination triggers, FlowCord splits the buttons into pages and injects Next and Previous buttons into the reserved row.

Explicit configuration

Pass a pagination option to .setButtons() to control page size or opt in explicitly before the limit is reached:

.setButtons((ctx) => [
/* ... many buttons ... */
], { pagination: { perPage: 10 } })
OptionTypeDefaultDescription
perPagenumber25Maximum buttons per page (excluding fixed-position buttons)
stableButtonsbooleantrueAlways show both Next/Previous buttons — disabled when not applicable
labels.nextstring'Next →'Label for the Next button
labels.previousstring'← Previous'Label for the Previous button

Fixed-position buttons

Buttons with fixedPosition: 'start' or fixedPosition: 'end' are pinned to every page and do not cycle through pagination. They count toward the total component limit but not toward perPage.

.setButtons((ctx) => [
{
label: '← Back',
style: ButtonStyle.Secondary,
fixedPosition: 'start',
action: goBack(),
},
// ... paginated buttons ...
{
label: 'Done',
style: ButtonStyle.Primary,
fixedPosition: 'end',
action: closeMenu(),
},
], { pagination: { perPage: 5 } })

Pagination hooks

onNext and onPrevious fire when the user clicks the injected navigation buttons. ctx.pagination reflects the updated page at the time they fire:

.onNext((ctx) => {
console.log(`Now on page ${ctx.pagination?.currentPage + 1} of ${ctx.pagination?.totalPages}`);
})
note

Select menus cannot be combined with button pagination. If you need both, use a string select with its own options list, and keep the paginated buttons in a separate menu.


List pagination

List pagination is for paginating data — when a menu displays a slice of a larger dataset and the user pages through it. Configure it with .setListPagination():

.setListPagination({
getTotalQuantityItems: async (ctx) => {
return await db.countItems({ userId: ctx.interaction.user.id });
},
itemsPerPage: 5,
})

FlowCord calls getTotalQuantityItems before each render cycle and computes ctx.pagination. Your render callbacks use ctx.pagination to slice the data:

.setEmbeds(async (ctx) => {
const { startIndex, endIndex } = ctx.pagination!;
const items = await db.getItems({
userId: ctx.interaction.user.id,
offset: startIndex,
limit: endIndex - startIndex,
});

return [
new EmbedBuilder()
.setTitle('Your Items')
.setDescription(items.map((i) => `${i.name}`).join('\n')),
];
})

ctx.pagination reference

ctx.pagination is PaginationState | null. It is null when no pagination is active.

PropertyTypeDescription
currentPagenumberZero-based current page index
totalPagesnumberTotal number of pages
itemsPerPagenumberItems per page
totalItemsnumberTotal item count (from getTotalQuantityItems)
startIndexnumberInclusive start offset for the current page
endIndexnumberExclusive end offset for the current page

List pagination options

OptionTypeDefaultDescription
getTotalQuantityItems(ctx) => Awaitable<number>RequiredReturns the total item count
itemsPerPagenumber50Items per page
stableButtonsbooleantrueAlways render both nav buttons, disabled when N/A
labels.nextstring'Next →'Next button label
labels.previousstring'← Previous'Previous button label

List pagination works in both embeds and layout mode. Next and Previous buttons are injected into the reserved row automatically.


Choosing between them

Button paginationList pagination
Use whenYou have many buttons that need to be spread across pagesYou're rendering a paginated dataset
Configured via.setButtons(fn, { pagination }).setListPagination(opts)
ctx.paginationAvailable during renderAvailable during render
Can combine with select menuNoYes