Skip to main content

Pagination

Lists are paginated with cursors, following the Relay connection specification. The only list query that isn't paginated is parentCategories, which returns every top-level category at once. Inside a record, some lists are plain arrays returned in full, such as Invoice.items or Category.children. Others are connections, such as Product.variants, Variant.variantPrices or Variant.stockActivities: paginate them too.

How it works​

Each list returns a connection:

  • edges: the items of the page. Each edge has a node (the item) and a cursor (a pointer to that item).
  • pageInfo: startCursor and endCursor (the cursors of the first and last items of the page), hasNextPage and hasPreviousPage.
  • totalCount: the size of the whole list, not just of the page.

Ask for a page with first, then pass the endCursor you received as after to get the next page, until hasNextPage is false.

Page size​

  • Without first or last, a page has 20 items.
  • A page has 50 items at most. Asking for more returns an error with the message "fetching is limited to 50 edges". Its extensions.code depends on the list (UnexpectedPaginationArgLimit or INTERNAL_SERVER_ERROR): test the message, or never ask for more than 50.
  • Use first with after to go forward, or last with before to go back. Don't combine first with last or before, or last with after.

First page​

query StockActivities {
stockActivities(first: 10) {
totalCount
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
edges {
cursor
node {
id
}
}
}
}

Response:

{
"data": {
"stockActivities": {
"totalCount": 30,
"pageInfo": {
"startCursor": "<cursor of the first item>",
"endCursor": "<cursor of the last item>",
"hasNextPage": true,
"hasPreviousPage": false
},
"edges": [{ "cursor": "<cursor of the first item>", "node": { "id": "…" } }]
}
}
}

Next page​

Pass the endCursor of the previous page as after:

query NextStockActivities($after: String) {
stockActivities(first: 10, after: $after) {
pageInfo {
endCursor
hasNextPage
}
edges {
node {
id
}
}
}
}
{ "after": "<cursor of the last item>" }

Previous page​

Use last and before with the startCursor of the current page:

query PreviousStockActivities($before: String) {
stockActivities(last: 10, before: $before) {
pageInfo {
startCursor
hasPreviousPage
}
edges {
node {
id
}
}
}
}

Fetch a whole list​

Repeat the "next page" query until hasNextPage is false. Ask for the largest pages (first: 50, the maximum) to send fewer requests.

Cursors are positions in the list. If records are added or archived while you read, an item can appear twice or be skipped. Deduplicating by id removes the repeats, not the skips: to catch what you missed, use webhooks or, for variants, the updatedAt filter. The default order can differ between lists: don't rely on it.