Shopify bulk operations: moving a whole catalogue
Above a few thousand records, pagination is the wrong tool. Bulk operations are the difference between four minutes and four hours.
7 min read · APIs & data ·
Paginating through 50,000 products at 250 a time is 200 requests, each costing rate-limit budget, taking however long it takes. Bulk operations replace that with one request, an asynchronous job on Shopify's side, and a file you download when it's finished.
They're GraphQL-only, exempt from the normal cost limit, and the correct tool for anything touching a whole catalogue.
Bulk queries
Submit a query; Shopify runs it in the background:
``graphql mutation { bulkOperationRunQuery( query: """ { products { edges { node { id title status variants { edges { node { id sku inventoryQuantity price } } } } } } } """ ) { bulkOperation { id status } userErrors { field message } } } ``
Note there's no first: — that's the point. You're asking for everything.
Then poll for completion, or better, subscribe to the bulk_operations/finish webhook so Shopify tells you:
``graphql query { currentBulkOperation { id status errorCode objectCount url } } ``
When status is COMPLETED, url is a temporary link to a JSONL file — one JSON object per line.
Reading JSONL
The format is flat, not nested. A product and its variants come out as separate lines, with child records carrying a __parentId pointing at their parent:
`` {"id":"gid://shopify/Product/1","title":"Chair"} {"id":"gid://shopify/ProductVariant/11","sku":"CH-1","__parentId":"gid://shopify/Product/1"} {"id":"gid://shopify/ProductVariant/12","sku":"CH-2","__parentId":"gid://shopify/Product/1"} ``
So you stream the file and reassemble relationships from __parentId. This matters practically: stream it, don't load it into memory. A large catalogue's export is hundreds of megabytes, and the naive approach works on a development store and falls over on a real one.
The URL expires, so download promptly.
Bulk mutations
The same idea in reverse. Write a JSONL file of inputs, upload it to Shopify's staged upload target, then run:
``graphql mutation { bulkOperationRunMutation( mutation: "mutation call($input: ProductInput!) { productUpdate(input: $input) { product { id } userErrors { field message } } }" stagedUploadPath: "tmp/12345/bulk/uploaded-file.jsonl" ) { bulkOperation { id status } userErrors { field message } } } ``
Each line of the file is one set of variables. When it completes, you get a results file — read it, because per-record failures appear there rather than as an error on the operation. An operation that reports COMPLETED can still contain thousands of individual failures.
The limits worth knowing
- One bulk operation of each type at a time per app per store. Queue your own work accordingly.
- Large operations take real time — minutes to hours depending on catalogue size. Design for asynchronous, not for a request that waits.
- Result URLs expire.
- Not everything is available in bulk queries; check the specific resource.
When to use them
Use bulk for: initial catalogue import or export, full nightly reconciliation, mass price updates, bulk metafield writes, any report over the whole catalogue.
Use normal queries for: anything under a few thousand records, anything needing an immediate answer, single-record reads and writes, webhook-driven updates.
The rule of thumb: more than a few thousand records, or you don't know how many, use bulk.
Where they fit in an integration
A well-built ERP integration is usually three mechanisms working together:
- Webhooks for immediate reaction to individual changes.
- Normal API calls for the small, urgent writes.
- A nightly bulk operation that reconciles everything and catches what webhooks dropped.
That third one is the reconciliation job, and it's what separates an integration that's usually right from one that's reliably right. Bulk operations are what make it affordable to run every night.
Bulk operations are the difference between a nightly sync that finishes before the warehouse opens and one that's still running when someone asks why stock is wrong.
Is this the problem you’re looking at?
Send me the link to your store and a line about what is going wrong. You get a straight answer within one business day — no pitch, no obligation.
mario@clicksandcarts.coOr see what I do around Shopify: services, work beyond the theme, selected work.