# 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.

Page: https://lintelagency.com/resources/graphql/cursor-pagination

## 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.

## Facts

- **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:** 2 November 2026

## How to use it

1. Send ProductsPage with first: 250 and after: null.
2. Process data.products.nodes, then read pageInfo.endCursor.
3. Send the same query again with after set to that cursor; keep first, query and sortKey unchanged.
4. Stop when hasNextPage is false. paginate.js shows the loop with error handling.

## Code

### products-page.graphql

```graphql
query 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

```json
{
  "first": 250,
  "after": "eyJsYXN0X2lkIjoxMjM0NTY3ODkwLCJsYXN0X3ZhbHVlIjoiMTIzNDU2Nzg5MCJ9",
  "query": "status:active updated_at:>=2026-10-01"
}
```

### products-page.response.json

```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

```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);
}
```

## 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

- [Paginating results with GraphQL](https://shopify.dev/docs/api/usage/pagination-graphql)

## Related

- [Read query cost and throttle status](https://lintelagency.com/resources/graphql/query-cost-and-throttle-status)
- [Export all products with a bulk operation](https://lintelagency.com/resources/graphql/bulk-export-products)
- [Read stock levels by location](https://lintelagency.com/resources/graphql/inventory-levels-by-location)

License: MIT. Free to use in your own store and in client work.
