Skip to main content

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:

InputDefault
first20
afterBeginning of results
sortid
sortDirectionasc

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​

  • hasNextPage indicates whether another matching item exists after the returned page.
  • endCursor identifies the position of the final returned item for the next request.
  • An empty page has data: [], hasNextPage: false, and endCursor: 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​

ValueMeaning
idService-generated ID
dateModifiedService-owned last-modified time
sortStringCustomer-provided string
sortNumberCustomer-provided integer
sortDateCustomer-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.

Was this page helpful?