I am also trying to use the Shopify Admin GraphQL API with Python. I was able to find the correct solution using the AI Assistant in the Shopify GraphQL API documentation.
Below is the code, which includes comments. I have also included screenshots of the metafield settings and the bulk operation results.
import requests
import json
def fill_metafields_in_products_TEST():
# * Variables
# ACCESS_TOKEN = "shpat_****"
# STORE_NAME = "test-dev-store-****" # without .myshopify.com
# GRAPHQL_VER = "2025-07"
# GRAPHQL_ENDPOINT = (
# "https://"
# + STORE_NAME
# + ".myshopify.com/admin/api/"
# + GRAPHQL_VER
# + "/graphql.json"
# )
headers = {
"Content-Type": "application/json",
"X-Shopify-Access-Token": ACCESS_TOKEN,
}
# 1. Find ONLY ONE product, at least with 2 variants and query only first 5.
# Also Querying metafields with namespace
find_product_query = """
{
products(first: 1, query: "variants_count:>=2") {
edges {
node {
id
title
variants(first: 5) {
edges {
node {
id
title
sku
metafields(first: 10, namespace: "custom") {
edges {
node {
id
key
value
namespace
}
}
}
}
}
}
}
}
}
}
"""
response = requests.post(
GRAPHQL_ENDPOINT, json={"query": find_product_query}, headers=headers
)
data = response.json()
# Check, if product was found
if not data.get("data") or not data["data"]["products"]["edges"]:
print("[ERROR] No products found with two or more variants")
return
# 2. Process first product
product = data["data"]["products"]["edges"][0]
product_id = product["node"]["id"]
product_title = product["node"]["title"]
variants = product["node"]["variants"]["edges"]
print(f"[DEBUG] Processing product with title: '{product_title}'")
# * Preparing data for bulk update
variants_data = []
for variant in variants:
variant_id = variant["node"]["id"]
variant_title = variant["node"]["title"]
# Check, if this variant's metafield is already filled
existing_metafield_id = None
if variant["node"]["metafields"]["edges"]:
for metafield in variant["node"]["metafields"]["edges"]:
# NOTE Queried metafields with our namespace, so it's combination of namespace+key
if metafield["node"]["key"] == "variant_description":
existing_metafield_id = metafield["node"]["id"]
break
# * Correct format for rich text field
rich_text_value = {
"type": "root",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "text",
"value": f"Variant description: This is cool {variant_title}, description success!",
"bold": True,
},
{
"type": "text",
"value": "\n\nThis product variant description was automatically added.",
},
{
"type": "text",
"value": f"\n\nProduct title: {product_title}",
},
{
"type": "text",
"value": f"\n\nVariant title: {variant_title}",
},
],
}
],
}
# Making data for metafields
metafields = []
if existing_metafield_id:
# If exist, only updating (without specifying type)
metafields.append(
{"id": existing_metafield_id, "value": json.dumps(rich_text_value)}
)
else:
# Creating new metafield with correct type
metafields.append(
{
"namespace": "custom",
"key": "variant_description",
"value": json.dumps(rich_text_value),
"type": "rich_text_field",
}
)
variants_data.append({"id": variant_id, "metafields": metafields})
# 3. Performing bulk mutation
bulk_mutation = """
mutation productVariantsBulkUpdate($productId: ID!, $variants: [ProductVariantsBulkInput!]!) {
productVariantsBulkUpdate(productId: $productId, variants: $variants) {
product {
id
title
}
productVariants {
id
title
metafields(first: 5) {
edges {
node {
id
namespace
key
value
type
}
}
}
}
userErrors {
field
message
}
}
}
"""
variables = {"productId": product_id, "variants": variants_data}
mutation_response = requests.post(
GRAPHQL_ENDPOINT,
json={"query": bulk_mutation, "variables": variables},
headers=headers,
)
result = mutation_response.json()
print(f"[DEBUG] bulk update result: {json.dumps(result, indent=2)}")
# Processing result
if "errors" in result:
print(f"[ERROR] Error in bulk update: {result['errors']}")
return
if "data" in result:
bulk_result = result["data"]["productVariantsBulkUpdate"]
if bulk_result.get("userErrors"):
print(f"[ERROR] Errors while bulk updating: {bulk_result['userErrors']}")
else:
print(f"✅ Product updated successfully: {bulk_result['product']['title']}")
for variant in bulk_result["productVariants"]:
print(f" ✅ Variant: {variant['title']}")
for metafield in variant["metafields"]["edges"]:
metafield_data = metafield["node"]
print(
f" 📝 Metafield: {metafield_data['namespace']}.{metafield_data['key']}"
)
print("[DEBUG] Variants metafields processed via bulk API!")
if __name__ == "__main__":
fill_metafields_in_products_TEST()
Metafield in settings: chrome-8-B7wt-Jlyi-I hosted at ImgBB — ImgBB
Result in the product variant: HGfye2-LBBL hosted at ImgBB — ImgBB