Focusing on managing products, variants, and collections through the API.
I am trying to figure out how to upload image files to Shopify
the flow of the process is like this.
Create a staged target > Upload the file to the staged target > attach the resource url to product create mutation input.
what happens is that sometimes images get successfully uploaded and products have the images but sometimes they don't. if the file size is large, it always fails.
my code related to the process is below. I use GraphQl and then send image files via Axios. And then add the resource url to the product input. Since sometimes it gets successful, I assume my code about product create mutation is correct, and the problem is with the uploading process.
It seems a lot of people seem to experience this issue with this api, so I'm wondering if anyone could help me out.
I am sorry this is a lot of code
const UPLOADMUTATIONQUERY = gql`
mutation stagedUploadsCreate($input: [StagedUploadInput!]!) {
stagedUploadsCreate(input: $input) {
stagedTargets {
url
resourceUrl
parameters {
name
value
}
# StagedMediaUploadTarget fields
}
userErrors {
field
message
}
}
}
`;
const decodeBase64 = (data: string) => {
return Buffer.from(data, `base64`);
};
const PRODUCTDMUTATIONQUERY = gql`
mutation productCreate($input: ProductInput!, $media: [CreateMediaInput!]) {
productCreate(input: $input, media: $media) {
product {
# Product fields
id
}
userErrors {
field
message
}
}
}
`;
const uploadImage = async (img: any) => {
const { data } = await client.mutate({
mutation: UPLOADMUTATIONQUERY,
variables: {
input: {
fileSize: String(img.fileSize),
filename: img.fileName,
httpMethod: `POST`,
mimeType: img.mimeType,
resource: `PRODUCT_IMAGE`,
},
},
});
return { data };
};
const createProduct = async (input: any, media: any) => {
const { data } = await client.mutate({
mutation: PRODUCTDMUTATIONQUERY,
variables: {
input: input,
media: media,
},
});
if (
data.productCreate.userErrors &&
data.productCreate.userErrors.length > 0
) {
throw new Error(data.productCreate.userErrors.message);
}
return { data };
};
const makeImagePayload = (imgUrls: string[]) => {
return imgUrls.map((url) => {
return {
alt: `a`,
mediaContentType: `IMAGE`,
originalSource: url,
};
});
};
const uploadFile = async (
params: { name: any; value: any }[],
url: string,
resourceUrl: any,
file: Buffer,
fileSize: number,
) => {
const formData = new FormData();
// Add each of the params we received from Shopify to the form. this will ensure our ajax request has the proper permissions and s3 location data.
params.forEach(({ name, value }) => {
formData.append(name, value);
});
formData.append(`file`, file);
return await axios
.post(url, formData, {
headers: {
...formData.getHeaders(),
'Content-Length': String(fileSize + 5000),
},
})
.then((response) => console.log(response.data))
.catch((err) => console.log(err));
};
const uploadMasterImages = async (imgs: any[]): Promise<string[]> => {
const result = await Promise.all(
imgs.map(async (img) => {
const encoded = Buffer.from(img.data, `base64`);
const { data } = await uploadImage(img);
// Save the target info.
const target = data.stagedUploadsCreate.stagedTargets[0];
const params = target.parameters; // Parameters contain all the sensitive info we'll need to interact with the aws bucket.
const url = target.url; // This is the url you'll use to post data to aws. It's a generic s3 url that when combined with the params sends your data to the right place.
const resourceUrl = `https://shopify.s3.amazonaws.com/${params[0].value}`; // This is the specific url that will contain your image data after you've uploaded the file to the aws staged target.
uploadFile(params, url, resourceUrl, encoded, img.fileSize);
// if(errors && errors.length > 0){
// throw new Error('画像投稿中にエラーが発生いたしました。リトライしてください。')
// }
return resourceUrl;
}),
);
return result;
};
const uploadSlaveImage = async (img: any) => {
const encoded = Buffer.from(img.data, `base64`);
const { data } = await uploadImage(img);
// Save the target info.
const target = data.stagedUploadsCreate.stagedTargets[0];
const params = target.parameters; // Parameters contain all the sensitive info we'll need to interact with the aws bucket.
const url = target.url; // This is the url you'll use to post data to aws. It's a generic s3 url that when combined with the params sends your data to the right place.
const resourceUrl = `https://shopify.s3.amazonaws.com/${params[0].value}`; // This is the specific url that will contain your image data after you've uploaded the file to the aws staged target.
uploadFile(params, url, resourceUrl, encoded, img.fileSize);
// if(errors && errors.length > 0){
// throw new Error('画像投稿中にエラーが発生いたしました。リトライしてください。')
// }
return resourceUrl;
};
export const batchCreateProducts = async (body: FormValues) => {
const result = await uploadMasterImages(body.masterImages);
const masterImagePayload = await makeImagePayload(result);
const masterVariants = body.sizes.map((size) => {
return {
price: body.minPrice,
options: size.name,
};
});
const masterPayload = {
title: body.title,
descriptionHtml: body.desc,
options: [`サイズ`],
variants: masterVariants,
vendor: body.vendor,
productType: body.productType,
tags: `master`,
metafields: [
{
description: ``,
key: `features`,
namespace: `global`,
value: body.features,
type: `multi_line_text_field`,
},
{
description: ``,
key: `material`,
namespace: `global`,
value: body.material,
type: `multi_line_text_field`,
},
{
description: `商品コード`,
key: `productCode`,
namespace: `global`,
value: body.productCode,
type: `multi_line_text_field`,
},
],
};
createProduct(masterPayload, masterImagePayload);
//master商品の追加
//slave商品の追加
return body.slaves.forEach(async (slave, i) => {
const slaveVariants = body.sizes.map((size) => {
return {
price: size.price ? size.price : body.minPrice,
options: [size.name, slave.name, slave.code],
};
});
const resourceUrl = await uploadSlaveImage(slave.image);
const slavePayload = {
title: body.title,
handle: body.title + i,
options: [`サイズ`, `色`, `色コード`],
variants: slaveVariants,
tags: `slave`,
};
return createProduct(slavePayload, {
alt: `alt`,
mediaContentType: `IMAGE`,
originalSource: resourceUrl,
});
});
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
try {
batchCreateProducts(req.body);
} catch (error) {
console.log(error);
res.status(500).json({
message: `エラーが発生しました、リトライするかリロードして最初からやり直してください。`,
});
}
}
when I upload the same pics to google cloud storage and use the public urls, it worked. So I guess there is something wrong with the api or the code. Or because the resource urls were not public
Hi @kunio I'm running into the same issue, can you please help?
https://community.shopify.com/c/graphql-basics-and/image-file-upload-through-graphql-api-processing-...
any update on this issue? I also have the same issue