MetafieldsSet query as bulk operation/mutation

Topic summary

Availability and scope: The metafieldsSet mutation is supported as a bulk operation via bulkOperationRunMutation, despite documentation examples focusing on products/collections.

How to structure the bulk mutation:

  • GraphQL mutation: metafieldsSet(metafields: [MetafieldsSetInput!]!).
  • JSONL (newline-delimited JSON) upload: each line should have a top-level “metafields” array containing MetafieldsSetInput objects.
  • Do not wrap payloads with an “input” object.
  • You can include multiple metafields in the array on a single line (e.g., to set several keys for the same ownerId).
  • Each MetafieldsSetInput must include: key, namespace, type, value, and ownerId (Shopify GID, e.g., gid://shopify/ProductVariant/… ).

Context and alternatives:

  • Using productUpdate for metafields is possible but requires metafield IDs; metafieldsSet avoids that requirement.

Outcome/status:

  • Clarification provided with the correct mutation signature and JSONL shape. The thread concludes with a working format and no remaining open questions.
Summarized with AI on January 1. AI used: gpt-5.

Is the metafieldsSet query available as a bulk query? The documentation for bulk import is unclear as to whether or not you can create metafields using bulk queries. The metafieldsSet query is listed as a bulk mutation under the Limitations header of the docs but a little bit further down under the section Create a bulk mutation it only mentions product & collection mutations, if someone could clarify this it would be highly appreciated.

I have read that it’s possible to bulk create/update metafields using the productUpdate mutation but this seems more cumbersome because you have to include the metafield id which I want to avoid by using the metafieldsSet query if that is somehow possible.

Hi @SilasGrygier :waving_hand:

The metafieldsSet mutation is supported as a bulk operation with bulkOperationRunMutation. You should be able to pass the below in a string format, as the mutation argument. Keep in mind that the uploaded JSONL file will need to contain MetafieldSetInput objects as well.

mutation call($metafields: [MetafieldsSetInput!]!) {
    metafieldsSet(metafields: $metafields) {
        metafields {
            key
            namespace
            value
            createdAt
            updatedAt
        }
    }
}

Hope that helps!

1 Like

I’m performing this bulk update right now. I think I have the shape of my jsonl file wrong. An example line from my file:

{“input”: {“metafields”: [{“key”: “aims_sku”, “value”: “SGSKCAT_OWHT_OS”, “namespace”: “custom”, “ownerId”: “gid://shopify/productVariant/37140809220272”, “type”: “single_line_text_field”}, {“key”: “gs1_gtin”, “value”: “00198465367131”, “ownerId”: “gid://shopify/productVariant/37140809220272”, “type”: “single_line_text_field”, “namespace”: “custom”}]}}

But based on the MetafieldSetInput object you referenced, am I only allowed to have one metafield per line, and each line should look like:

{“input”: {“key”: “aims_sku”, “value”: “SGSKCAT_OWHT_OS”, “namespace”: “custom”, “ownerId”: “gid://shopify/productVariant/37140809220272”, “type”: “single_line_text_field”}}

or is it something else?

mutation call($metafields: [MetafieldsSetInput!]!) {
    metafieldsSet(metafields: $metafields) {
        metafields {
            key
            namespace
            value
            createdAt
            updatedAt
        }
    }
}

And your input should look like this:

{"metafields":[{"key": "price", "value": "25.55","namespace": "kickee", "ownerId": "gid://shopify/ProductVariant/43114979786990", "type":"number_decimal"},   {"key": "compare_at_price", "value": "36.5","namespace": "kickee", "ownerId": "gid://shopify/ProductVariant/43114979786990", "type":"number_decimal"}]}
{"metafields":[{"key": "price", "value": "25.55","namespace": "kickee", "ownerId": "gid://shopify/ProductVariant/43114979819758", "type":"number_decimal"},   {"key": "compare_at_price", "value": "36.5","namespace": "kickee", "ownerId": "gid://shopify/ProductVariant/43114979819758", "type":"number_decimal"}]}
{"metafields":[{"key": "price", "value": "25.55","namespace": "kickee", "ownerId": "gid://shopify/ProductVariant/43114979852526", "type":"number_decimal"},   {"key": "compare_at_price", "value": "36.5","namespace": "kickee", "ownerId": "gid://shopify/ProductVariant/43114979852526", "type":"number_decimal"}]}
{"metafields":[{"key": "price", "value": "25.55","namespace": "kickee", "ownerId": "gid://shopify/ProductVariant/43114979885294", "type":"number_decimal"},   {"key": "compare_at_price", "value": "36.5","namespace": "kickee", "ownerId": "gid://shopify/ProductVariant/43114979885294", "type":"number_decimal"}]}
{"metafields":[{"key": "price", "value": "25.55","namespace": "kickee", "ownerId": "gid://shopify/ProductVariant/43114979918062", "type":"number_decimal"},   {"key": "compare_at_price", "value": "36.5","namespace": "kickee", "ownerId": "gid://shopify/ProductVariant/43114979918062", "type":"number_decimal"}]}

Basically, you don’t need the {“input”: container

Hope that helps!