Export all products with a bulk operation

Export every Shopify product and variant to a JSONL file with bulkOperationRunQuery, then poll the operation and download the result.

API
Admin GraphQL API
Operations
bulkOperationRunQuery, bulkOperation
Access scope
read_products
Replaces (REST)
GET /admin/api/products.json with page_info paging
API version
2026-10
Last checked

In short

To export all products from Shopify, start a bulk operation with the bulkOperationRunQuery mutation, poll it with the bulkOperation query until its status is COMPLETED, then download the JSONL file from its url. Bulk operations skip pagination and rate limits for large exports. Each line is one object; variants carry a __parentId pointing to their product.

How to use it

  1. Send the StartProductExport mutation with the product query as the query variable. Put the fields you need inside it; connections need no first or after.
  2. Keep the returned bulk operation ID and poll ProductExportStatus every few seconds, or subscribe to the bulk_operations/finish webhook.
  3. When status is COMPLETED, download the file at url. The link expires after a week.
  4. Read the file line by line. Rebuild products from variants with __parentId.

Code

bulk-operation-run-query.graphql
mutation StartProductExport($query: String!) {
  bulkOperationRunQuery(query: $query) {
    bulkOperation {
      id
      status
    }
    userErrors {
      field
      message
    }
  }
}
bulk-operation-run-query.variables.json
{
  "query": "{ products { edges { node { id handle title status vendor productType tags variants { edges { node { id sku price inventoryQuantity } } } } } } }"
}
bulk-operation-run-query.response.json
{
  "data": {
    "bulkOperationRunQuery": {
      "bulkOperation": {
        "id": "gid://shopify/BulkOperation/1234567890",
        "status": "CREATED"
      },
      "userErrors": []
    }
  }
}
bulk-operation-status.graphql
query ProductExportStatus($id: ID!) {
  bulkOperation(id: $id) {
    id
    status
    errorCode
    objectCount
    fileSize
    url
    partialDataUrl
  }
}
bulk-operation-status.variables.json
{
  "id": "gid://shopify/BulkOperation/1234567890"
}
bulk-operation-status.response.json
{
  "data": {
    "bulkOperation": {
      "id": "gid://shopify/BulkOperation/1234567890",
      "status": "COMPLETED",
      "errorCode": null,
      "objectCount": "2483",
      "fileSize": "1048576",
      "url": "https://storage.googleapis.com/shopify-tiers-assets-prod-us-east1/bulk-operation-outputs/example.jsonl",
      "partialDataUrl": null
    }
  }
}
result.jsonl
{"id":"gid://shopify/Product/1111111111","handle":"linen-shirt","title":"Linen shirt","status":"ACTIVE","vendor":"Your Brand","productType":"Shirts","tags":["summer"]}
{"id":"gid://shopify/ProductVariant/2222222222","sku":"LS-S","price":"49.00","inventoryQuantity":12,"__parentId":"gid://shopify/Product/1111111111"}
{"id":"gid://shopify/ProductVariant/3333333333","sku":"LS-M","price":"49.00","inventoryQuantity":0,"__parentId":"gid://shopify/Product/1111111111"}

Written by Lintel. MIT license: free to use in your own store and in client work.

Limits and edge cases

  • An app can run up to five bulk query operations at the same time.
  • If status is FAILED, errorCode says why; partialDataUrl may hold what was exported before the failure.
  • objectCount and fileSize come back as strings, because they can be larger than a JavaScript number.
  • Nested connections are limited to two levels inside a bulk query.

Questions

How do I export all Shopify products with GraphQL?
Use bulkOperationRunQuery with a products query, wait for the operation to complete, then download the JSONL file from the url field. For a few hundred products a normal paginated products query also works.
Why is the result JSONL and not JSON?
So it can be read one line at a time without loading the whole export into memory. Child objects such as variants are separate lines with a __parentId.
Can I include metafields in the export?
Yes. Add metafields { edges { node { namespace key value } } } inside the product node; each metafield becomes its own line with the product as __parentId.
  • 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.

Have something to build? Let’s talk it through.

Taking a limited number of projects at a time. Every brief is read by an engineer, not a salesperson.

Gencer KarakayaShopify Engineer

An engineer reads your brief, and we reply by email.