Cursor pagination and sorting
Operations using cursor pagination
listArrayItems and lookupArrayItems use forward-only cursor pagination. There are no backward-pagination arguments.
Requesting the first page
Defaults:
| Input | Default |
|---|---|
first | 20 |
after | Beginning of results |
sort | id |
sortDirection | asc |
first must be between 1 and 500 inclusive.
Example: First page sorted by date
query ListRecentFavorites($input: ListArrayItemsInput!) {
listArrayItems(input: $input) {
totalCount
data { id itemId value sortDate }
pageInfo { hasNextPage endCursor }
}
}
{
"input": {
"scope": "USER",
"key": "content:favorites",
"first": 2,
"sort": "sortDate",
"sortDirection": "desc"
}
}
{
"data": {
"listArrayItems": {
"totalCount": 2,
"data": [
{
"id": "66b100000000000000000001",
"itemId": "movie:100",
"value": {
"title": "Example Movie",
"mediaType": "MOVIE"
},
"sortDate": "2026-09-18T10:30:00.000Z"
}
],
"pageInfo": {
"hasNextPage": true,
"endCursor": "<opaque cursor returned by the service>"
}
}
}
}
Requesting the next page
Pass the previous page's endCursor as after and keep the normalized query unchanged:
{
"input": {
"scope": "USER",
"key": "content:favorites",
"first": 2,
"after": "<opaque cursor returned by the service>",
"sort": "sortDate",
"sortDirection": "desc"
}
}
The page size may change between requests. The cursor remains bound to the operation, query, scope, key, sort, and direction.
Page information
hasNextPageindicates whether another matching item exists after the returned page.endCursoridentifies the position of the final returned item for the next request.- An empty page has
data: [],hasNextPage: false, andendCursor: null.
Cursor rules
Cursors are opaque
Cursors are service-generated positions. Do not decode, construct, modify, or treat them as permanent item identities.
Query binding
A listArrayItems cursor is bound to the operation, authenticated scope, key, sort property, and sort direction. A lookupArrayItems cursor is additionally bound to the canonical selector sets.
Using a cursor with a different operation, scope, key, sort, direction, or lookup selector set fails with CURSOR_CONSTRAINT. Selector order does not matter after normalization.
Changing page size
first is not cursor-bound and may change between requests, subject to the 1–500 limit.
Sorting
Supported sort properties
| Value | Meaning |
|---|---|
id | Service-generated ID |
dateModified | Service-owned last-modified time |
sortString | Customer-provided string |
sortNumber | Customer-provided integer |
sortDate | Customer-provided date |
Sort direction and ties
sortDirection is asc or desc. Equal primary values use the service-generated ID as the deterministic secondary key, in the same direction.
Items without the selected sort field remain traversable. Do not assume selector order controls result order.
Understanding totalCount
listArrayItems consistency
listArrayItems.totalCount describes the complete array, but the count and returned page are not snapshot-consistent. Concurrent writes can make them describe different moments.
lookupArrayItems consistency
lookupArrayItems.totalCount and its page share one request snapshot. Separate cursor requests use separate snapshots.
Concurrent changes during traversal
Cursor pagination avoids offset shifting, but it is not a multi-request snapshot. Inserts, deletes, and changes to an item's active sort value can affect later pages. A deleted cursor anchor does not prevent traversal from continuing.
Pagination errors
Invalid first returns PAGINATION_CONSTRAINT with field: first and constraint: range.
Malformed, unsupported-version, or query-mismatched cursors return CURSOR_CONSTRAINT with field: after and a corresponding cursor constraint. Inspect errors[].extensions; do not parse message text.
See Errors and limits.