Importing large volumes of data using traditional and synchronous APIs is slow, complex to run, and difficult to manage. Instead of manually running a GraphQL mutation multiple times and managing a client-side throttle, you can run a bulk mutation operation. Using the GraphQL Admin API, you can bulk import large volumes of data asychronously. When the operation is complete, the results are delivered in a [JSON Lines (JSONL)](https://jsonlines.org/) file that Shopify makes available at a URL. This guide introduces the [`bulkOperationRunMutation`](/docs/api/admin-graphql/latest/mutations/bulkoperationrunmutation) and shows you how to use it to bulk import data into Shopify. ## Requirements - You're familiar with creating [products](/docs/api/admin-graphql/latest/mutations/productcreate), [product variants](/docs/api/admin-graphql/latest/mutations/productvariantcreate), and [collections](/docs/api/admin-graphql/latest/mutations/collectioncreate) in your development store. - You're familiar with [performing bulk operations](/docs/api/usage/bulk-operations/queries) using the GraphQL Admin API. ## Limitations - You can run only one bulk operation of each type ([`bulkOperationRunMutation`](/docs/api/admin-graphql/latest/mutations/bulkoperationrunmutation) or [`bulkOperationRunQuery`](/docs/api/admin-graphql/latest/mutations/bulkoperationrunquery)) at a time per shop. - The bulk mutation operation has to complete within 24 hours. After that it will be stopped and marked as `failed`. When your import runs into this limit, consider reducing the input size. - You can supply only one of the supported GraphQL API mutations to the `bulkOperationRunMutation` at a time: - [`collectionCreate`](/docs/api/admin-graphql/latest/mutations/collectioncreate) - [`collectionUpdate`](/docs/api/admin-graphql/latest/mutations/collectionupdate) - [`customerCreate`](/docs/api/admin-graphql/latest/mutations/customercreate) - [`customerUpdate`](/docs/api/admin-graphql/latest/mutations/customerupdate) - [`customerPaymentMethodRemoteCreate`](/docs/api/admin-graphql/latest/mutations/customerpaymentmethodremotecreate) - [`giftCardCreate`](/docs/api/admin-graphql/latest/mutations/giftcardcreate) - [`giftCardUpdate`](/docs/api/admin-graphql/latest/mutations/giftcardupdate) - [`marketingActivityUpsertExternal`](/docs/api/admin-graphql/latest/mutations/marketingActivityUpsertExternal) - [`marketingEngagementCreate`](/docs/api/admin-graphql/latest/mutations/marketingEngagementCreate) - [`metafieldsSet`](/docs/api/admin-graphql/latest/mutations/metafieldsset) - [`metaobjectUpsert`](/docs/api/admin-graphql/latest/mutations/metaobjectupsert) - [`priceListFixedPricesAdd`](/docs/api/admin-graphql/latest/mutations/pricelistfixedpricesadd) - [`priceListFixedPricesDelete`](/docs/api/admin-graphql/latest/mutations/pricelistfixedpricesdelete) - [`privateMetafieldUpsert`](/docs/api/admin-graphql/latest/mutations/privatemetafieldupsert) - [`productCreate`](/docs/api/admin-graphql/latest/mutations/productcreate) - [`productSet`](/docs/api/admin-graphql/latest/mutations/productSet) - [`productUpdate`](/docs/api/admin-graphql/latest/mutations/productupdate) - [`productUpdateMedia`](/docs/api/admin-graphql/latest/mutations/productupdatemedia) - [`productVariantsBulkCreate`](/docs/api/admin-graphql/latest/mutations/productVariantsBulkCreate) - [`productVariantsBulkDelete`](/docs/api/admin-graphql/latest/mutations/productVariantsBulkDelete) - [`productVariantsBulkReorder`](/docs/api/admin-graphql/latest/mutations/productVariantsBulkReorder) - [`productVariantsBulkUpdate`](/docs/api/admin-graphql/latest/mutations/productVariantsBulkUpdate) - [`publishablePublish`](/docs/api/admin-graphql/latest/mutations/publishablePublish) - [`publishableUnpublish`](/docs/api/admin-graphql/latest/mutations/publishableUnpublish) - [`publicationUpdate`](/docs/api/admin-graphql/latest/mutations/publicationUpdate) - [`storeCreditAccountCredit`](/docs/api/admin-graphql/latest/mutations/storecreditaccountcredit) - [`subscriptionBillingAttemptCreate`](/docs/api/admin-graphql/latest/mutations/subscriptionbillingattemptcreate) - [`subscriptionContractActivate`](/docs/api/admin-graphql/latest/mutations/subscriptioncontractactivate) - [`subscriptionContractAtomicCreate`](/docs/api/admin-graphql/latest/mutations/subscriptioncontractatomiccreate) - [`subscriptionContractCancel`](/docs/api/admin-graphql/latest/mutations/subscriptioncontractcancel) - [`subscriptionContractExpire`](/docs/api/admin-graphql/latest/mutations/subscriptioncontractexpire) - [`subscriptionContractFail`](/docs/api/admin-graphql/latest/mutations/subscriptioncontractfail) - [`subscriptionContractPause`](/docs/api/admin-graphql/latest/mutations/subscriptioncontractpause) - [`subscriptionContractProductChange`](/docs/api/admin-graphql/latest/mutations/subscriptioncontractproductchange) - [`subscriptionContractSetNextBillingDate`](/docs/api/admin-graphql/latest/mutations/subscriptioncontractsetnextbillingdate) - The mutation that's passed into `bulkOperationRunMutation` is limited to one connection field, which is defined by the GraphQL Admin API schema. - The size of the JSONL file cannot exceed 20MB. ## How bulk importing data works You initiate a bulk operation by supplying a mutation string in the `bulkOperationRunMutation`. Shopify then executes that mutation string asynchronously as a bulk operation. Most GraphQL Admin API requests that you make are subject to [rate limits](/docs/api/usage/rate-limits), but the `bulkOperationRunMutation` request isn't. Because you're only making low-cost requests for creating operations, polling their status, or canceling them, bulk mutation operations are an efficient way to create data compared to standard GraphQL API requests. The following diagram shows the steps involved in bulk importing data into Shopify: ![Workflow for bulk importing data](/assets/api/tutorials/bulk-import-data.png) 1. **Create a JSONL file and include GraphQL variables**: Include the variables for the mutation in a JSONL file format. Each line in the JSONL file represents one input unit. The mutation runs once on each line of the input file. 2. **Upload the file to Shopify**: Before you upload the file, you must reserve a link by running the [`stagedUploadsCreate`](/docs/api/admin-graphql/latest/mutations/stageduploadscreate) mutation. After the space has been reserved, you can upload the file by making a request using the information returned from the [`stagedUploadsCreate`](/docs/api/admin-graphql/latest/mutations/stageduploadscreate) response. 3. **Create a bulk mutation operation**: After the file has been uploaded, you can run [`bulkOperationRunMutation`](/docs/api/admin-graphql/latest/mutations/bulkoperationrunmutation) to create a bulk mutation operation. The [`bulkOperationRunMutation`](/docs/api/admin-graphql/latest/mutations/bulkoperationrunmutation) imports data in bulk by running the supplied GraphQL API mutation with the file of variables uploaded in the last step. 4. **Wait for the operation to finish**: To determine when the bulk mutation has finished, you can either: 1. **Subscribe to a webhook topic**: You can use the [`webhookSubscriptionCreate`](/docs/api/admin-graphql/latest/mutations/webhooksubscriptioncreate) mutation to subscribe to the `bulk_operations/finish` webhook topic in order to receive a webhook when any operation finishes - in other words, it has completed, failed, or been cancelled. 2. **Poll the status of the operation**: While the operation is running, you can poll to see its progress using the [`currentBulkOperation`](/docs/api/admin-graphql/latest/objects/queryroot) field. The `objectCount` field on the [`bulkOperation`](/docs/api/admin-graphql/latest/objects/bulkoperation) object increments to indicate the operation's progress, and the `status` field returns a boolean value that states whether the operation is completed. 5. **Retrieve the results**: When a bulk mutation operation is completed, a JSONL output file is available for download at the URL specified in the `url` field. ## Create a JSONL file and include GraphQL variables When adding GraphQL variables to a new JSONL file, you need to format the variables so that they are accepted by the corresponding bulk operation GraphQL API. The format of the input variables need to match the GraphQL Admin API schema. For example, you might want to import a large quantity of products. Each attribute of a product must be mapped to existing fields defined in the GraphQL input object [`ProductInput`](/docs/api/admin-graphql/latest/input-objects/productinput). In the JSONL file, each line represents one product input. The GraphQL Admin API runs once on each line of the input file. One input should take up one line only, no matter how complex the input object structure is. The following example shows a sample JSONL file that is used to create 10 products in bulk: ```json { "input": { "title": "Sweet new snowboard 1", "productType": "Snowboard", "vendor": "JadedPixel" } } { "input": { "title": "Sweet new snowboard 2", "productType": "Snowboard", "vendor": "JadedPixel" } } { "input": { "title": "Sweet new snowboard 3", "productType": "Snowboard", "vendor": "JadedPixel" } } { "input": { "title": "Sweet new snowboard 4", "productType": "Snowboard", "vendor": "JadedPixel" } } { "input": { "title": "Sweet new snowboard 5", "productType": "Snowboard", "vendor": "JadedPixel" } } { "input": { "title": "Sweet new snowboard 6", "productType": "Snowboard", "vendor": "JadedPixel" } } { "input": { "title": "Sweet new snowboard 7", "productType": "Snowboard", "vendor": "JadedPixel" } } { "input": { "title": "Sweet new snowboard 8", "productType": "Snowboard", "vendor": "JadedPixel" } } { "input": { "title": "Sweet new snowboard 9", "productType": "Snowboard", "vendor": "JadedPixel" } } { "input": { "title": "Sweet new snowboard 10", "productType": "Snowboard", "vendor": "JadedPixel" } } ``` > Note: > The GraphQL Admin API doesn't serially process the contents of the JSONL file. Avoid relying on a particular sequence of lines and object order to achieve a desired result. ## Upload the file to Shopify After you've created the JSONL file, and included the GraphQL variables, you can upload the file to Shopify. Before uploading the file, you need to first generate the upload URL and parameters. ### Generate the uploaded URL and parameters You can use the [`stagedUploadsCreate`](/docs/api/admin-graphql/latest/mutations/stageduploadscreate) mutation to generate the values that you need to authenticate the upload. The mutation returns an array of [`stagedMediaUploadTarget`](/docs/api/admin-graphql/latest/objects/stagedmediauploadtarget) instances. An instance of [`stagedMediaUploadTarget`](/docs/api/admin-graphql/latest/objects/stagedmediauploadtarget) has the following key properties: - `parameters`: The parameters that you use to authenticate an upload request. - `url`: The signed URL where you can upload the JSONL file that includes GraphQL variables. The mutation accepts an input of type [`stagedUploadInput`](/docs/api/admin-graphql/latest/input-objects/stageduploadinput), which has the following fields: | Field | Type | Description | |---|---|---| | `resource` | [enum](/docs/api/admin-graphql/latest/enums/stageduploadtargetgenerateuploadresource) | Specifies the resource type to upload. To use `bulkOperationRunMutation`, the resource type must be `BULK_MUTATION_VARIABLES`. | | `filename` | [string](/docs/api/admin-graphql/latest/scalars/String) | The name of the file to upload. | | `mimeType` | [string](/docs/api/admin-graphql/latest/scalars/String) | The [media type](https://en.wikipedia.org/wiki/Media_type) of the file to upload. To use `bulkOperationRunMutation`, the `mimeType` must be `"text/jsonl"`. | | `httpMethod` | [enum](/docs/api/admin-graphql/latest/enums/stageduploadhttpmethodtype) | The HTTP method to be used by the staged upload. To use `bulkOperationRunMutation`, the `httpMethod` must be `POST`. | #### Example The following example uses the [`stagedUploadsCreate`](/docs/api/admin-graphql/latest/mutations/stageduploadscreate) mutation to generate the values required to upload a JSONL file and be consumed by the [`bulkOperationRunMutation`](/docs/api/admin-graphql/latest/mutations/bulkoperationrunmutation). You must first run the `stagedUploadsCreate` mutation with no variables, and then separately send a POST request to the staged upload URL with the JSONL data: