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

Page: https://lintelagency.com/resources/graphql/bulk-export-products

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

## Facts

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

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

```graphql
mutation StartProductExport($query: String!) {
  bulkOperationRunQuery(query: $query) {
    bulkOperation {
      id
      status
    }
    userErrors {
      field
      message
    }
  }
}
```

### bulk-operation-run-query.variables.json

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

```json
{
  "data": {
    "bulkOperationRunQuery": {
      "bulkOperation": {
        "id": "gid://shopify/BulkOperation/1234567890",
        "status": "CREATED"
      },
      "userErrors": []
    }
  }
}
```

### bulk-operation-status.graphql

```graphql
query ProductExportStatus($id: ID!) {
  bulkOperation(id: $id) {
    id
    status
    errorCode
    objectCount
    fileSize
    url
    partialDataUrl
  }
}
```

### bulk-operation-status.variables.json

```json
{
  "id": "gid://shopify/BulkOperation/1234567890"
}
```

### bulk-operation-status.response.json

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

```text
{"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"}
```

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

## Shopify documentation

- [Perform bulk operations with the GraphQL Admin API](https://shopify.dev/docs/api/usage/bulk-operations/queries)
- [bulkOperationRunQuery](https://shopify.dev/docs/api/admin-graphql/latest/mutations/bulkOperationRunQuery)

## Related

- [Keeping inventory in sync with another system](https://lintelagency.com/journal/sync-inventory-external-system)
- [Migrating to Shopify: what breaks and how to plan for it](https://lintelagency.com/journal/migrating-to-shopify-what-breaks)

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