Hello Shopify Community,
I am encountering a recurring issue with the productSet
bulk mutation using the GraphQL Admin API (version 2024-07
, which uses media, and 2024-10
, which uses files). Some listings return the following userError
:
“Something went wrong, please try again.”
Problem Description:
- Intermittent Behavior: The error does not consistently occur. When I retry the request with the same data, it often works without issues.
- Generic Error Message: The error message is vague, making it challenging to debug the underlying problem.
- Listings Are Not Created: The listings are often not successfully created in Shopify.
Steps to Reproduce:
GET valid data for the next environment variables
const SHOP_DOMAIN = process.env. SHOP_DOMAIN;
const ACCESS_TOKEN = process.env.SHOPIFY_ACCESS_TOKEN;
Run the POC script that reproduces the issue:
// try to associate images at the end:
const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');
const path = require('path');
const uuidv4 = require('uuid').v4;
// Replace these with your actual Shopify shop domain and access token
const SHOP_DOMAIN = 'vela-test-xavor-large-5-dev.myshopify.com';
const ACCESS_TOKEN = process.env.SHOPIFY_ACCESS_TOKEN;
// Shopify API version
const API_VERSION = '2024-10';
// Shopify GraphQL endpoint
const GRAPHQL_ENDPOINT = `https://${SHOP_DOMAIN}/admin/api/${API_VERSION}/graphql.json`;
// Your provided image data (flattened the array for easier access)
const images = [
{
"originalSource": "https://s3.amazonaws.com/dev-images.getvela.com/cb14481cdfb11a02343a1c6f322b200c438fbbc5",
"contentType": "PRODUCT_IMAGE",
"alt": "a dog that is laying down with its mouth open"
},
];
// Your provided product data
const products = [
// First product data
{
"seo": {
"title": "1Luxurious Cotton Dragon T-Shirt with Elegant Gold Detail Perfect for Any Occasion",
"description": "Te presentamos la camiseta DRAGÓN, elaborada en algodón de lujo con detalles dorados. Esta camiseta combina comodidad y estilo, perfecta para cualquier ocasión. Su diseño exclusivo y los lujosos acabados en oro la convierten en una opción única para tu guardarropa."
},
"vendor": "large-5-dev",
"variants": [
{
"optionValues": [{ "optionName": "Title", "name": "Default Title" }],
"inventoryPolicy": "DENY",
"compareAtPrice": 0,
"position": 1,
"taxable": true,
"price": 1,
"barcode": null,
"sku": null
}
],
"title": "1Luxurious Cotton Dragon T-Shirt with Elegant Gold Detail Perfect for Any Occasion",
"tags": ["artistic flair", "casual day outfit", "charm and sophistication", "comfortable clothing", "cotton blend", "dragon t-shirt", "elegant gold detail", "everyday look", "exceptional shirt", "exquisite details", "fashion statement", "gold accents", "luxurious cotton t-shirt", "modern design", "night out attire", "personal style", "polished appearance", "relaxed vibe", "sophisticated fashion", "standout clothing", "stylish apparel", "tailored pants", "timeless elegance", "unique dragon design", "versatile wardrobe staple"],
"status": "DRAFT",
"productType": "",
"productOptions": [{ "name": "Title", "position": 1, "values": [{ "name": "Default Title" }] }],
"collections": [],
"descriptionHtml": "Introducing the **Dragon T-Shirt**, crafted from luxurious cotton and embellished with exquisite gold details. This exceptional shirt seamlessly blends comfort and style, making it an ideal addition to your wardrobe for any occasion. The softness of the cotton ensures that you feel as good as you look.The **unique dragon design** adds an artistic flair, ensuring you stand out wherever you go. The subtle gold accents impart sophistication and charm, subtly elevating your everyday look. Whether for a night out or a casual day, this versatile piece effortlessly transitions between different settings. Pair it with your favorite jeans for a relaxed vibe, or dress it up with tailored pants for a more polished appearance.Opt for this distinctive item that not only showcases your personal style but also provides the comfort you desire throughout the day. Don't miss the chance to enhance your collection with the timeless elegance and unique allure of the **Dragon T-Shirt**. Explore its charm today!",
"handle": `${Date.now()}${Math.floor(Math.random() * 1000000)}`
},
{
"seo": {
"title": "2Luxurious Cotton Dragon T-Shirt with Elegant Gold Detail Perfect for Any Occasion",
"description": "Te presentamos la camiseta DRAGÓN, elaborada en algodón de lujo con detalles dorados. Esta camiseta combina comodidad y estilo, perfecta para cualquier ocasión. Su diseño exclusivo y los lujosos acabados en oro la convierten en una opción única para tu guardarropa."
},
"vendor": "large-5-dev",
"variants": [
{
"optionValues": [{ "optionName": "Title", "name": "Default Title" }],
"inventoryPolicy": "DENY",
"compareAtPrice": 0,
"position": 1,
"taxable": true,
"price": 1,
"barcode": null,
"sku": null
}
],
"title": "second product",
"tags": ["artistic flair", "casual day outfit", "charm and sophistication", "comfortable clothing", "cotton blend", "dragon t-shirt", "elegant gold detail", "everyday look", "exceptional shirt", "exquisite details", "fashion statement", "gold accents", "luxurious cotton t-shirt", "modern design", "night out attire", "personal style", "polished appearance", "relaxed vibe", "sophisticated fashion", "standout clothing", "stylish apparel", "tailored pants", "timeless elegance", "unique dragon design", "versatile wardrobe staple"],
"status": "DRAFT",
"productType": "",
"productOptions": [{ "name": "Title", "position": 1, "values": [{ "name": "Default Title" }] }],
"collections": [],
"descriptionHtml": "Introducing the **Dragon T-Shirt**, crafted from luxurious cotton and embellished with exquisite gold details. This exceptional shirt seamlessly blends comfort and style, making it an ideal addition to your wardrobe for any occasion. The softness of the cotton ensures that you feel as good as you look.The **unique dragon design** adds an artistic flair, ensuring you stand out wherever you go. The subtle gold accents impart sophistication and charm, subtly elevating your everyday look. Whether for a night out or a casual day, this versatile piece effortlessly transitions between different settings. Pair it with your favorite jeans for a relaxed vibe, or dress it up with tailored pants for a more polished appearance.Opt for this distinctive item that not only showcases your personal style but also provides the comfort you desire throughout the day. Don't miss the chance to enhance your collection with the timeless elegance and unique allure of the **Dragon T-Shirt**. Explore its charm today!",
"handle": `${Date.now()}${Math.floor(Math.random() * 1000000)}`
},
{
"seo": {
"title": "11 oz Classic Ceramic Mug - Ideal Gift for Coffee and Tea Lovers",
"description": "Start your day in style! This 11oz ceramic mug is perfect for coffee, tea, or any of your favorite drinks. It features a comfortable handle and a vibrant, long-lasting design that will brighten every sip. Whether you're using it at home, in the office, or giving it as a gift, it's sure to become your go-to mug. Elevate your morning routine or make someone's day with this classic coffee mug. Looking for the perfect gift for the coffee or tea lover in your life? Look no further than this monogrammed leather Dopp kit. With a generous 255 character limit, you can add a personal touch to this ideal groomsmen gift. This high-quality leather kit is perfect for keeping toiletries organized on trips or at home. Treat yourself or someone special to this elegant and functional gift that is sure to impress. Order now and make any occasion even more special with this monogrammed leather Dopp kit."
},
"vendor": "large-5-dev",
"variants": [
{
"optionValues": [{ "optionName": "Title", "name": "Default Title" }],
"inventoryPolicy": "DENY",
"compareAtPrice": 0,
"position": 1,
"taxable": true,
"price": 0,
"barcode": null,
"sku": null
}
],
"title": "11 oz Classic Ceramic Mug - Ideal Gift for Coffee and Tea Lovers",
"tags": ["11 oz mug", "beverage enjoyment", "birthday gift", "ceramic mug", "classic mug", "coffee lovers", "coffee mug", "comfortable handle", "daily ritual", "drinking experience", "durable ceramic", "gift for her", "gift for him", "holiday gift", "home essentials", "kitchen collection", "morning routine", "office mug", "relaxation moments", "stylish drinkware", "tea lovers", "tea mug", "thoughtful gift", "unique gift", "vibrant design"],
"status": "DRAFT",
"productType": "",
"productOptions": [{ "name": "Title", "position": 1, "values": [{ "name": "Default Title" }] }],
"collections": [],
"descriptionHtml": "Start your day with style! This **11 oz classic ceramic mug** is perfect for enjoying your favorite coffee, tea, or any other beverage you cherish. Crafted from durable ceramic, this mug features a **comfortable handle** that ensures a secure grip, allowing you to savor each sip with ease. Its **vibrant design** adds a splash of color to your morning routine, making it suitable for both home and office use. Picture yourself taking a moment to unwind, with the steam rising from your drink as you indulge in rich flavors. Each time you fill this mug, it becomes part of your daily ritual.This **classic mug** also makes a thoughtful gift for coffee and tea enthusiasts. Whether for a birthday, holiday, or just to show you care, it’s a lovely way to express affection. With its timeless appeal, this ceramic mug is sure to become a favorite in any kitchen collection. Elevate your drinking experience with our **11 oz classic ceramic mug**—the perfect companion for those precious moments of relaxation and enjoyment. Order yours today and turn every sip into a delightful occasion!",
"handle": `${Date.now()}${Math.floor(Math.random() * 1000000)}`
}
];
// Polling function to check if all files are in the READY state
async function checkFilesReady(fileIds) {
const fileStatusQuery = `
query fileStatus($ids: [ID!]!) {
nodes(ids: $ids) {
... on File {
id
fileStatus
}
}
}
`;
const startTime = Date.now();
const timeout = 60000; // Set timeout to 60 seconds
while (Date.now() - startTime < timeout) {
try {
const response = await axios.post(
GRAPHQL_ENDPOINT,
{
query: fileStatusQuery,
variables: { ids: fileIds },
},
{
headers: {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': ACCESS_TOKEN,
}
}
);
const files = response.data.data.nodes;
const notReadyFiles = files.filter(file => file.fileStatus !== 'READY');
if (notReadyFiles.length === 0) {
console.log("All files are in READY state.");
return true;
} else {
console.log("Waiting for files to be in READY state...");
await new Promise(resolve => setTimeout(resolve, 1000));
}
} catch (error) {
console.error("Error checking file statuses:", error);
return false;
}
}
console.error("Timeout reached while waiting for files to be in READY state.");
return false;
}
// Mutation for file creation
const FILE_CREATE_MUTATION = `
mutation fileCreate($files: [FileCreateInput!]!) {
fileCreate(files: $files) {
files {
alt
createdAt
id
fileStatus
}
}
}
`;
// Step 1: Upload images for each product and collect media IDs
async function uploadImagesForProduct(productImages) {
try {
const fileInputs = productImages.map(image => ({
alt: image.alt,
originalSource: image.originalSource,
filename: uuidv4(),
}));
console.log("Uploading images...", fileInputs);
const response = await axios.post(
GRAPHQL_ENDPOINT,
{
query: FILE_CREATE_MUTATION,
variables: { files: fileInputs },
},
{
headers: {
'Content-Type': 'application/json',
'X-Shopify-Access-Token': ACCESS_TOKEN,
}
}
);
const { files, userErrors } = response.data.data.fileCreate;
console.log("Files created:", files);
if (userErrors && userErrors.length > 0) {
console.error("User errors during file creation:", userErrors);
return null;
}
const fileIds = files.map(file => file.id);
// Poll to ensure all files are in READY state
const allFilesReady = await checkFilesReady(fileIds);
if (!allFilesReady) {
throw new Error("Not all files reached READY state.");
}
return fileIds; // Return media IDs for this product
} catch (error) {
console.error("Error uploading images:", error);
return null;
}
}
// Step 2: Prepare product data with unique media IDs and write to JSONL file
async function prepareProductData() {
const jsonlPath = path.resolve(__dirname, 'products_upload.jsonl');
const writeStream = fs.createWriteStream(jsonlPath, { flags: 'w' });
for (const product of products) {
// Upload images specific to this product
const fileIds = await uploadImagesForProduct(images);
const files = fileIds.map(fileId => ({
id: fileId,
contentType: 'IMAGE',
duplicateResolutionMode: 'RAISE_ERROR'
}));
if (!fileIds) throw new Error("Error uploading images for product.");
console.log("File IDs for product:", fileIds);
const productEntry = {
input: {
...product,
files,
// mediaIds: fileIds,
}
};
writeStream.write(JSON.stringify(productEntry) + '\n');
}
writeStream.end();
console.log(`Products JSONL file created at ${jsonlPath}`);
return jsonlPath;
}
// Step 3: Stage JSONL file for bulk operation
async function stageJsonlFileForBulkOperation(filePath) {
const getStagedTargetMutation = (fileName) => `
mutation {
stagedUploadsCreate(input: {
resource: BULK_MUTATION_VARIABLES,
filename: "${fileName}",
mimeType: "text/jsonl",
httpMethod: POST
}) {
userErrors {
field
message
},
stagedTargets {
url,
resourceUrl,
parameters {
name
value
}
}
}
}
`;
try {
// Request upload target
const query = getStagedTargetMutation(path.basename(filePath));
const stagedUploadResponse = await axios.post(
GRAPHQL_ENDPOINT,
{ query },
{ headers: { 'X-Shopify-Access-Token': ACCESS_TOKEN } }
);
const stagedTargets = stagedUploadResponse.data?.data?.stagedUploadsCreate?.stagedTargets;
if (!stagedTargets || stagedTargets.length === 0) {
throw new Error('Failed to get a valid staged target.');
}
const stagedTarget = stagedTargets[0];
const parameters = stagedTarget.parameters || [];
// Find the parameter where name is 'key' and get its value
const stagedUploadPath = parameters.find(param => param.name === 'key')?.value;
if (!stagedUploadPath) {
throw new Error('Failed to get staged upload path.');
}
// Prepare FormData with required parameters and the file itself
const form = new FormData();
parameters.forEach(param => form.append(param.name, param.value));
form.append('file', fs.createReadStream(filePath));
// Upload the file to the staged URL
console.log('form headers:', form.getHeaders());
await axios.post(stagedTarget.url, form, { headers: form.getHeaders() });
console.log('JSONL file staged successfully for bulk operation at URL:', stagedTarget.url);
return stagedUploadPath; // Return the staged upload path for use in the bulk operation
} catch (error) {
console.error('Error staging JSONL file:', error.response ? error.response.data : error.message);
return null;
}
}
// Step 4: Start the bulk operation with the staged JSONL URL
async function startBulkOperation(stagedUploadPath) {
const bulkOperationQuery = `
mutation {
bulkOperationRunMutation(
mutation: """mutation call($input: ProductSetInput!) {
productSet(input: $input) {
product {
id
legacyResourceId
title
handle
publishedAt
variants(first: 2000) {
edges {
node {
id
inventoryItem {
id
legacyResourceId
}
}
}
}
}
userErrors {
code
field
message
}
}
}""",
stagedUploadPath: "${stagedUploadPath}") {
bulkOperation {
id
url
status
}
userErrors {
code
field
message
}
}
}
`;
try {
const response = await axios.post(GRAPHQL_ENDPOINT, { query: bulkOperationQuery }, { headers: { 'X-Shopify-Access-Token': ACCESS_TOKEN } });
const { bulkOperation, userErrors } = response.data.data.bulkOperationRunMutation;
if (userErrors && userErrors.length > 0) {
console.error("User errors during bulk operation initiation:", userErrors);
return null;
}
console.log('Bulk operation initiated with ID:', bulkOperation.id);
return bulkOperation.id;
} catch (error) {
console.error('Error initiating bulk operation:', error.response ? error.response.data : error.message);
return null;
}
}
// Step 5: Monitor the bulk operation status
async function monitorBulkOperation() {
return new Promise((resolve, reject) => {
const interval = setInterval(async () => {
try {
const query = `
{
currentBulkOperation (type: MUTATION) {
id
status
errorCode
createdAt
completedAt
objectCount
fileSize
url
}
}
`;
const response = await axios.post(GRAPHQL_ENDPOINT, { query }, { headers: { 'X-Shopify-Access-Token': ACCESS_TOKEN } });
console.log('Checking bulk operation status...');
const operation = response.data.data.currentBulkOperation;
console.log(`Bulk operation status: ${operation.status}`);
if (operation.status === 'COMPLETED') {
clearInterval(interval);
console.log('Bulk operation completed successfully.');
console.log('Results URL:', operation.url);
resolve(operation);
} else if (operation.status === 'FAILED') {
clearInterval(interval);
console.error('Bulk operation failed:', operation.errorCode);
reject(new Error(`Bulk operation failed with error code: ${operation.errorCode}`));
}
} catch (error) {
clearInterval(interval);
reject(error);
}
}, 1000);
});
}
// Main function to execute the steps sequentially
(async () => {
try {
const jsonlFilePath = await prepareProductData();
for (const product of products) {
// Upload images specific to this product
const fileIds = await uploadImagesForProduct(images);
console.log('fileIds:', JSON.stringify(fileIds.map(fileId => ({ id: fileId}))));
}
const stagedUploadPath = await stageJsonlFileForBulkOperation(jsonlFilePath);
if (stagedUploadPath) {
const bulkOperationId = await startBulkOperation(stagedUploadPath);
await monitorBulkOperation(bulkOperationId);
}
} catch (error) {
console.error('Error in bulk product upload:', error);
}
})();
- I upload images for products using the
fileCreate
mutation. - I include images in the bulk mutation via
productSet
once images are ready.
Expected Behavior:
The bulk mutation should process all items without returning generic errors when no issues exist with the input data.
Observed Behavior:
- Some listings return the error:
"Something went wrong, please try again."
- Listings that “failed” aren’t created successfully in the Shopify admin.
Questions:
- Is this error related to a known issue with the
productSet
bulk mutation? - Are additional debugging tools or logs available to better understand why this happens?
- Has anyone else observed this behavior with similar mutations?
Additional Information:
- GraphQL API Version: 2024-10 (files) and 2024-07 (mediaIds)
- Shopify Shop Domain:
vela-test-xavor-large-5-dev.myshopify.com
Any insights or recommendations would be greatly appreciated!
Thanks in advance,
Giovanni