Algolia Pagination: How page and perPage Actually Work (and the Off-by-One Mistake Most Teams Make)
Algolia's page parameter is zero-indexed and perPage caps at 100 on free plans. Here's the mechanism, a working client config, and the off-by-one and retry mistakes that break pagination.
20 Sept 2026, 14:19 UTC

Algolia paginates with two parameters: page, which is zero-indexed, and perPage, which caps out at 100 on free and trial plans. If you remember only one thing, remember the zero-indexing: page=0 is the first page, not the second. Most pagination bugs in Algolia integrations come from assuming it starts at 1.
The mechanism: offset pagination without an offset parameter
Algolia doesn't expose a raw offset parameter in its standard search API. Instead, the effective offset is computed as page × perPage. So page=3, perPage=20 skips the first 60 hits and returns hits 61–80. The response includes page, nbPages (total pages available), hitsPerPage, and nbHits (total matching records), which is everything a UI needs to render numbered pagination or a "load more" button.
A concrete client configuration
Using the JavaScript client (v4 syntax, run in any Node or browser environment with your app ID and a search-only API key):
import { algoliasearch } from "algoliasearch";
const client = algoliasearch("YOUR_APP_ID", "YOUR_SEARCH_ONLY_KEY");
const { results } = await client.search({
requests: [
{
indexName: "products",
query: "running shoes",
page: 3, // fourth page — zero-indexed
hitsPerPage: 20,
},
],
});
console.log(results[0].page); // 3
console.log(results[0].nbPages); // total pages at this page size
console.log(results[0].hits.length); // up to 20
The equivalent REST call is a POST to /1/indexes/*/queries with { "indexName": "products", "params": "query=running%20shoes&page=3&hitsPerPage=20" } in the body. Note the REST parameter name is hitsPerPage; some older client versions also accept perPage as an alias. Check which your installed client version expects — the v4 JavaScript client uses hitsPerPage.
Limits you will hit in production
- Page size cap. On free and trial plans,
hitsPerPageabove 100 is rejected with a 400 error or clamped, depending on the client. Paid plans can raise this, but verify against your own account rather than assuming — the limit is plan-specific. - Deep pagination ceiling. Algolia is designed for search, not full index export. By default, deep paging is bounded (historically around 1,000 hits retrievable via
page/hitsPerPage); thepaginationLimitedToindex setting controls how deep users can page. If you need to walk the entire index, use thebrowseendpoint with a cursor instead of incrementingpage. - Rate limits. A pagination loop that fires requests as fast as possible will get throttled. Watch the
x-algolia-ratelimit-remainingandx-algolia-ratelimit-resetresponse headers and back off before you hit zero.
The four mistakes that cause most pagination bugs
1. Treating page as 1-indexed
UI components (carousels, numbered pagers) almost universally label the first page "1". If you pass that label straight into page, you skip the first page of results entirely, and your last page duplicates the previous one. Always translate: page = uiPage - 1.
2. Assuming perPage above 100 works
On a free plan, requesting hitsPerPage: 500 either errors or silently truncates, which breaks any loop that assumes it got everything it asked for. Assert hits.length === hitsPerPage (except on the last page) if correctness matters.
3. Trusting facet counts during heavy writes
Facet counts returned alongside paginated results are computed at query time but the underlying index is updated asynchronously. During intensive write bursts, filter UIs can show counts that don't match what a click-through actually returns. This is expected eventual-consistency behavior, not a bug — design the UI to tolerate it.
4. Naive retry logic
If a throttled request for page=5 is retried after your loop already advanced its cursor, you can append page 5 twice. Track the last successfully fetched page and dedupe on hit objectID before rendering.
How to verify your pagination is correct
Write a small script that requests page=0, 1, 2 with hitsPerPage=5 against a stable test index, collect the objectID values, and assert two things: no ID repeats across pages, and the union of IDs matches the first 15 IDs from a single page=0, hitsPerPage=15 request. Run it in CI against a dedicated test index so write activity can't skew the comparison. In the Algolia dashboard, the query stats view lets you sanity-check that nbHits stays consistent across sequential page requests for the same query.
When not to use page at all
If you're building an infinite scroll, numbered page navigation works but wastes quota on users who never go deep. If you're exporting or syncing records, use browse — it's cursor-based, unaffected by paginationLimitedTo, and won't miss records inserted mid-iteration the way offset-style paging can. Reserve page/hitsPerPage for what it's built for: bounded, user-facing result navigation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.