# Programmatic Pagination with the SDK

> Current Froomle documentation · [View canonical page](https://docs.froomle.com/froomle-doc-main/v2/js_lib/programmatic-pagination.html)

Use pagination when your application renders a ranked recommendation list or a SmartSort result in pages. Request the full ranking with `list_size` and return only the first page with `limit`. Froomle must enable pagination for your environment before later pages are available.

The SDK offers two ways to retrieve more items:

| First request | Page-number method | Result of a later page |
| --- | --- | --- |
| `getRecommendations(…​)` with `list_size` greater than `limit` | `response.lists()[i].pagination.getPage(pageNumber)` | One `RecommendationList` with `items`. Each list in a multi-list response can be paged independently. |
| `smartSort({ candidates, limit, pagination: true })` | `result.pagination.getPage(pageNumber)` | A SmartSort result with `items`, `entries`, and `sortedCandidates` mapped to the original candidates. |
| Either first request, if you saved its `listKey` | `getListSlice({ listKey, offset, limit })` | A `Recommendations` response containing raw recommendation items. Use this for an arbitrary rank range or after a page reload. |

Choose `pagination.getPage(pageNumber)` when you have the first result in memory and want consecutive pages at its fixed `limit`. Use the recommendation list’s helper for `getRecommendations(…​)`; use the SmartSort result’s helper when you need candidate objects mapped on every page. Choose `getListSlice(…​)` when you only have a saved `listKey` after a reload or need a different starting rank or count. A slice gives you recommendation items; your application maps SmartSort item IDs back to products.

`pageNumber` starts at **1**: the initial result is page 1. `getPage(2)` uses the initial `limit` as its fixed page size. `offset` in `getListSlice` is also **1-based**, but means the **rank of the first item to return**, not a page number or a count of skipped items.

| Initial `limit` | Page number | Slice `{ offset, limit }` | Ranks requested |
| --- | --- | --- | --- |
| 20 | 1 | `{ offset: 1, limit: 20 }` | 1 through 20 |
| 20 | 2 | `{ offset: 21, limit: 20 }` | 21 through 40 |
| 20 | 3 | `{ offset: 41, limit: 20 }` | 41 through 60 |

The SDK does not provide a total page count. A list can contain fewer items than `list_size`, and its final page can be short or empty. Your application owns page controls, rendering, and recommendation impression/click tracking.

<a id="_page_through_recommendations"></a>

## Page through recommendations

`getRecommendations(…​)` returns a `Recommendations` object, even when pagination is available. Read the list you want to display from `lists()`, then page that list. The `pagination` property sits beside its `items`.

```ts
import { getRecommendations } from '@froomle/frontend-sdk'

const response = await getRecommendations([
  { list_name: 'recommended_for_you', list_size: 100, limit: 20 },
])
const firstList = response.lists()[0]
renderItems(firstList.items) // Ranks 1 through 20

if (firstList.pagination) {
  const secondList = await firstList.pagination.getPage(2)
  renderItems(secondList.items) // Ranks 21 through 40, if available
}
```

`firstList.pagination.pageNumber` is `1`, `pageSize` is `20`, and `offset` is `1`. The second list has `pageNumber: 2`, `pageSize: 20`, and `offset: 21`. Subsequent `getPage(…​)` calls keep the first page’s size. If the initial response has no usable `list_key`, `pagination` is `null` and only the initial items are available.

<a id="_page_through_smartsort_results"></a>

## Page through SmartSort results

Pass the full candidate set to `smartSort(…​)`. The SDK ranks that set once and returns the first `limit` items. Set `pagination: true` to get page controls on the result.

```ts
import { smartSort } from '@froomle/frontend-sdk'

const firstPage = await smartSort({
  list_name: 'search_results_rerank',
  candidates: allMatchingProducts,
  getId: (product) => product.sku,
  getItemType: () => 'product',
  limit: 24,
  pagination: true,
})
renderProducts(firstPage.sortedCandidates) // Ranks 1 through 24

const secondPage = await firstPage.pagination.getPage(2)
renderProducts(secondPage.sortedCandidates) // Ranks 25 through 48, if available
```

Each page also has `items`, `entries`, and `pagination`. The SDK maps later pages back to the candidates supplied in the first call, so this helper needs that first result in memory. Paginated SmartSort results do not include `unmatchedCandidates`: candidates absent from one page can appear on another. `firstPage.pagination.listKey` is `null` when later pages are unavailable; in that case, `getPage(2)` rejects. Customer-managed benchmark control order cannot be combined with SmartSort pagination. See [Programmatic SmartSort](https://docs.froomle.com/froomle-doc-main/v2/js_lib/smart-sorting-reranking.html#SmartSortProgrammatic) for candidate mapping and tracking details.

<a id="_retrieve_a_rank_range_by_list_key"></a>

## Retrieve a rank range by list key

Save the key from `firstList.pagination.listKey` or `firstPage.pagination.listKey` while it is available. `getListSlice(…​)` works with keys from either flow, including in a later request after a page reload. Choose any positive starting rank and count; this call does not inherit the first page’s `limit`.

```ts
import { getListSlice } from '@froomle/frontend-sdk'

// savedListKey came from a recommendation list or a SmartSort result.
const slice = await getListSlice({
  listKey: savedListKey,
  offset: 25,
  limit: 12,
})
const rankedItems = slice.lists()[0].items // Ranks 25 through 36, if available
```

The return value is a `Recommendations` object: use `slice.lists()[0].items` or `slice.items()`. It has no attached `pagination.getPage(…​)` helper. For a SmartSort key, it does not restore the original candidate objects or `sortedCandidates`; use the returned item IDs to load your product data and preserve the returned rank order.

The saved key is valid only while the ranked list is available. If it expires, the API returns HTTP 404 and you must make a new initial request. The SDK rejects missing keys, non-integer page numbers, and offsets or limits below 1 before requesting a later page. See [Manual API pagination](https://docs.froomle.com/froomle-doc-main/v2/guide/manual-integration/recommendations.html#manual-recommendation-pagination) for the underlying endpoint contract.
