Page through results with cursors
Read every product, order or customer page by page: ask for pageInfo, pass endCursor as after, and stop when hasNextPage is false.
- API
- Admin GraphQL API (Storefront API works the same way)
- Arguments
- first/after forward, last/before backward
- Page size
- Up to 250 nodes
- Replaces (REST)
- page_info links in the REST Link header
- API version
- 2026-10
- Last checked
In short
Shopify GraphQL connections use cursor-based pagination. Ask for up to 250 nodes with first, and select pageInfo { hasNextPage endCursor }. For the next page, send the same query with after set to the previous endCursor, and stop when hasNextPage is false. Keep the query and sortKey the same between pages. For more than 25,000 objects, use a bulk operation instead.
How to use it
- Send ProductsPage with first: 250 and after: null.
- Process data.products.nodes, then read pageInfo.endCursor.
- Send the same query again with after set to that cursor; keep first, query and sortKey unchanged.
- Stop when hasNextPage is false. paginate.js shows the loop with error handling.
Code
products-page.graphqlquery ProductsPage($first: Int!, $after: String, $query: String) {
products(first: $first, after: $after, query: $query, sortKey: ID) {
nodes {
id
handle
updatedAt
}
pageInfo {
hasNextPage
endCursor
}
}
}
products-page.variables.json{
"first": 250,
"after": "eyJsYXN0X2lkIjoxMjM0NTY3ODkwLCJsYXN0X3ZhbHVlIjoiMTIzNDU2Nzg5MCJ9",
"query": "status:active updated_at:>=2026-10-01"
}
products-page.response.json{
"data": {
"products": {
"nodes": [
{"id": "gid://shopify/Product/1234567891", "handle": "linen-shirt", "updatedAt": "2026-10-28T09:12:44Z"},
{"id": "gid://shopify/Product/1234567892", "handle": "wool-scarf", "updatedAt": "2026-10-30T15:03:10Z"}
],
"pageInfo": {
"hasNextPage": false,
"endCursor": "eyJsYXN0X2lkIjoxMjM0NTY3ODkyLCJsYXN0X3ZhbHVlIjoiMTIzNDU2Nzg5MiJ9"
}
}
}
}
paginate.js// Reads every page of a connection by passing pageInfo.endCursor back as
// `after` until hasNextPage is false. Node 18+ (global fetch).
const API_VERSION = '2026-10';
const PAGE_SIZE = 250;
export async function* allProducts({shop, token, document, query}) {
let after = null;
do {
const response = await fetch(`https://${shop}/admin/api/${API_VERSION}/graphql.json`, {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-Shopify-Access-Token': token},
body: JSON.stringify({query: document, variables: {first: PAGE_SIZE, after, query}}),
});
if (!response.ok) throw new Error(`Shopify returned HTTP ${response.status}`);
const body = await response.json();
if (body.errors) throw new Error(`GraphQL errors: ${JSON.stringify(body.errors)}`);
const {nodes, pageInfo} = body.data.products;
yield* nodes;
after = pageInfo.hasNextPage ? pageInfo.endCursor : null;
} while (after);
}
Written by Lintel. MIT license: free to use in your own store and in client work.
Limits and edge cases
- Paging is capped at 25,000 objects per connection, and counts stop at 25,001. Narrow the query filter or use a bulk operation for more.
- Cursors are opaque strings: store them as they are and don’t build them yourself.
- Bigger pages cost more per request; check the query cost if you also page nested connections.
- To go backward, use last and before with pageInfo.startCursor and hasPreviousPage.
Questions
- How does pagination work in the Shopify GraphQL API?
- With cursors. Each page returns pageInfo.endCursor; pass it as after to get the next page until hasNextPage is false.
- What is the maximum page size?
- 250 nodes per page with first or last.
- How do I get all products when there are more than 25,000?
- Use bulkOperationRunQuery, which returns everything as one JSONL file, or split the read with query filters such as created_at ranges.
Shopify documentation
- Validated against the Admin or Storefront API schema for version 2026-10 on the date shown. Check the version in your request URL.
- IDs and store addresses in the examples are placeholders. Replace them with your own.
- Admin API calls need an access token from your app. Never put an Admin API token in a storefront or theme.
- MIT license. Use the code in your own store or in client work.
Need this built, tested and maintained for your store? See API integrations.
