DEV Community

Cover image for Shopify GraphQL Pagination: How to Handle Large Datasets Without Slowing Down Your App
Muhammad Masad Ashraf
Muhammad Masad Ashraf

Posted on • Originally published at kolachitech.com

Shopify GraphQL Pagination: How to Handle Large Datasets Without Slowing Down Your App

When you build Shopify apps or integrations, pagination becomes important very quickly.

A small test store may have a few products and orders.

A real merchant store can have thousands of products, variants, orders, customers, inventory items, metafields, and fulfillment records.

You cannot fetch all of that data in one Shopify GraphQL request.

You need pagination.

More importantly, you need pagination that performs well.

Poor Shopify GraphQL pagination can create slow syncs, API throttling, timeout errors, duplicate processing, and incomplete exports.

This post explains how Shopify GraphQL pagination works and how to handle large Shopify datasets in a practical way.

What Shopify GraphQL Pagination Solves

Pagination lets your app retrieve data in smaller chunks.

Instead of asking Shopify for 50,000 products at once, your app asks for 100 or 250 products per request.

Shopify returns the data and gives your app information about the next page.

This protects your app from huge responses and protects Shopify from heavy requests.

It also gives your integration more control over retries, progress tracking, and background processing.

Shopify Uses Cursor-Based Pagination

Shopify GraphQL uses cursor-based pagination.

That means you do not request data using page numbers.

You request the next page using a cursor from the previous response.

A basic product pagination query looks like this:

query GetProducts($cursor: String) {
  products(first: 100, after: $cursor) {
    nodes {
      id
      title
      handle
      updatedAt
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The first time you run this query, pass cursor as null.

Shopify returns the first 100 products and gives you an endCursor.

Use that endCursor as the after value in the next request.

Keep doing this until hasNextPage is false.

Why Cursors Work Better Than Page Numbers

Offset pagination usually works like this:

page=1
page=2
page=3
Enter fullscreen mode Exit fullscreen mode

or:

offset=5000&limit=100
Enter fullscreen mode Exit fullscreen mode

This approach becomes inefficient when datasets grow.

The system may need to skip many records before returning the next page.

It also becomes less reliable when data changes while your app is reading it.

Shopify data changes constantly.

Orders are created. Products are updated. Inventory changes. Customers register.

Cursor-based pagination handles this type of data better because it continues from a known position.

Page Size Is a Performance Decision

Shopify GraphQL connections can return up to 250 resources in one page.

That does not mean you should always use first: 250.

For simple queries, 250 may work well.

Example:

products(first: 250) {
  nodes {
    id
    title
  }
}
Enter fullscreen mode Exit fullscreen mode

But if your query loads nested data, 250 can become too heavy.

Example:

products(first: 250) {
  nodes {
    id
    title
    variants(first: 50) {
      nodes {
        id
        sku
        inventoryQuantity
        metafields(first: 20) {
          nodes {
            key
            value
          }
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This query may look convenient, but it can become expensive because it fetches products, variants, inventory, and metafields together.

For complex queries, use a smaller page size.

A practical starting point:

Query Type Page Size
Basic products 100 to 250
Products with variants 50 to 100
Orders with line items 25 to 100
Metafield-heavy queries 25 to 100
Admin UI lists 20 to 50
Background sync jobs 100 to 250

The right number depends on query cost, response size, processing time, and failure rate.

Watch GraphQL Query Cost

GraphQL performance is not only about request count.

Shopify calculates query cost.

A simple query costs less.

A nested query costs more.

If you try to fetch everything in one request, you may hit throttling or timeout issues faster.

Instead of creating one huge query, split the workflow.

For example:

  1. Fetch product IDs and basic product fields.
  2. Save them locally.
  3. Fetch variants in a separate job.
  4. Fetch metafields only when needed.
  5. Process everything through a queue.

This keeps your queries smaller and easier to retry.

A Reliable Pagination Loop

A production pagination loop should not be a basic while loop only.

It should track progress and recover safely.

A good flow looks like this:

1. Start with cursor = null
2. Request a page
3. Process records
4. Save endCursor
5. Check hasNextPage
6. Respect throttle status
7. Retry failed requests with backoff
8. Resume from saved cursor if the job stops
Enter fullscreen mode Exit fullscreen mode

You should also make processing idempotent.

For example, if you sync products into your own database, use the Shopify product ID as a unique key and upsert the record.

Do not blindly insert records.

If your worker crashes and retries the same page, idempotent logic prevents duplicate data.

Use Filters Before Pagination

Do not paginate more data than you need.

If you only need recent orders, filter by date.

Example:

query GetRecentOrders($cursor: String) {
  orders(first: 100, after: $cursor, query: "created_at:>=2026-01-01") {
    nodes {
      id
      name
      createdAt
      displayFinancialStatus
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This reduces the number of pages your app needs to process.

Filters are especially useful for incremental syncs.

Instead of syncing all products every time, sync records updated after the last successful sync.

When to Use Bulk Operations

Cursor pagination is great for many use cases.

Use it for:

  • Admin tables
  • Small exports
  • Search results
  • Incremental syncs
  • Recent orders
  • Focused data reads

But for very large exports, use Shopify Bulk Operations.

Bulk Operations work better for:

  • Full product catalog exports
  • Historical order exports
  • Large customer exports
  • ERP syncs
  • Large metafield audits
  • Reporting pipelines

With Bulk Operations, Shopify processes the job asynchronously and gives you a file to download when it is ready.

This avoids thousands of paginated API calls.

Use Webhooks for Real-Time Updates

Pagination should not replace webhooks.

Use webhooks when you need real-time updates.

Use pagination for backfills, audits, and reconciliation.

A better architecture looks like this:

Webhook receives new order
Queue stores the event
Worker fetches missing details with GraphQL
Scheduled job reconciles recent records
Bulk Operation handles full historical export
Enter fullscreen mode Exit fullscreen mode

This approach keeps your app fast and reduces unnecessary API calls.

It also helps when webhook delivery is delayed or when your system needs to verify data consistency.

Common Mistakes

Here are mistakes I often see in Shopify GraphQL pagination workflows:

Mistake Problem
Always using first: 250 Heavy queries may slow down
Fetching too many nested fields Query cost increases
Not saving cursors Failed jobs restart from zero
Ignoring throttling API calls start failing
No retry logic Temporary errors break syncs
No idempotency Duplicate records appear
Using pagination for huge exports Bulk Operations would perform better
No logs or alerts Failures stay hidden

Most pagination issues happen because developers treat the job like a simple data loop.

For production apps, treat it like a data pipeline.

Final Thoughts

Shopify GraphQL pagination is one of the most important patterns for scalable Shopify apps.

Cursor-based pagination helps your app fetch large datasets in manageable chunks.

But good performance depends on more than cursors.

You need lean queries, smart page sizes, filters, saved cursors, retries, queues, webhook support, and Bulk Operations for very large datasets.

For small and medium workflows, cursor pagination is usually enough.

For large exports, Bulk Operations are the better option.

For real-time updates, use webhooks.

The strongest Shopify integrations combine all three.

Originally published on KolachiTech: https://kolachitech.com/shopify-graphql-pagination/

Top comments (0)