docs/pagination.md
Search queries can match many documents. Pagination lets you fetch and display results in chunks, keeping responses small and fast.
By default, Bleve returns the first 10 hits sorted by relevance (score), highest first.
From/Size: simple and stateless; cost grows with page depth.SearchAfter/SearchBefore: efficient for deep paging; requires passing sort keys from the previous page.Both modes can be used with any valid sort.
Size/FromOffset-based pagination uses Size (page length) and From (number of hits to skip). Bleve collects at least Size + From ordered results, then returns the Size slice starting at From.
JSON example:
{
"query": { "match": "California" },
"sort": ["-_score"],
"size": 5,
"from": 10
}
The result would be 5 hits starting from the 11th hit.
When to use:
From for deeper pages.SearchAfter and SearchBeforeThis returns the next (or previous) page based on a boundary defined by the sort keys of a specific hit. This keeps resource usage proportional to the page size, even for deep pages.
Rules:
SearchAfter (forward) or SearchBefore (backward), not both at once.SearchAfter/SearchBefore must match the length of Sort.Sort.query and sort across pages for consistent navigation.Where do sort keys come from?
Sort (and DecodedSort from Bleve v2.5.2). Take the last hit's sort keys for SearchAfter, or the first hit's sort keys for SearchBefore.Sort field may have garbled values; this is because of how Bleve represents such data types internally. To use such fields as sort keys, use the DecodedSort field, which decodes the internal representations. This feature is available from Bleve v2.5.4.When using
DecodedSort, theSortarray in the search request needs to explicitly declare the type of the field for proper decoding. Hence, theSortarray must contain eitherSortFieldobjects (for numeric and datetime) orSortGeoDistanceobjects (for geo) rather than just the field names. More info onSortFieldandSortGeoDistancecan be found in sort_facet.md.
Forward pagination over _id and _score:
{
"query": { "match": "California" },
"sort": ["_id", "_score"],
"search_after": ["hotel_10180", "0.998"],
"size": 3
}
Backward pagination over _id and _score:
{
"query": { "match": "California" },
"sort": ["_id", "_score"],
"search_before": ["hotel_17595", "0.623"],
"size": 4
}
Pagination using numeric, datetime, and geo fields. Notice how we specify the sort objects, with the "type" field explicitly declared in case of numeric and datetime:
{
"query": {
"match_all": {}
},
"size": 10,
"sort": [
{"by": "field", "field": "price", "type": "number"},
{"by": "field", "field": "created_at", "type": "date"},
{"by": "geo_distance", "field": "location", "location": {"lat": 40.7128, "lon": -74.0060}}
],
"search_after": ["99.99", "2023-10-15T10:30:00Z", "5.2"]
}
Pagination is deterministic. Ensure your Sort defines a total order, so that documents with the same sort keys are not left out:
- for descending), "_score", or "_id"."_id".["country", "-age", "_id"]["-_score", "_id"] (default score desc with a tie-breaker)From (collects at least Size + From results before slicing).SearchAfter/SearchBefore keeps memory and network proportional to Size.SearchAfter and SearchBefore.