A space to discuss GraphQL queries, mutations, troubleshooting, throttling, and best practices.
Hi!
I am developing a third-party application that uses the Shopify GraphQL API (version 2020.10) to add .mp4 videos to products. I have successfully completed the steps described in https://shopify.dev/api/examples/product-media (created an upload URL, uploaded the video and linked the video to a product). However, the following two issues appeared:
1. The last mutation, which links the video to the product does not return any sources (empty array) or originalSource (it is null) on Media nodes of type Video. However, the video is present on the actual product in the Shopify store. The mutation is presented below:
const LINK_MEDIA_TO_PRODUCT = gql`
mutation productCreateMedia($productId: ID!, $media: [CreateMediaInput!]!) {
productCreateMedia(productId: $productId, media: $media) {
media {
...fieldsForMediaTypes
}
mediaUserErrors {
code
field
message
}
product {
id
}
}
}
fragment fieldsForMediaTypes on Media {
alt
mediaContentType
preview {
image {
id
}
}
status
... on Video {
id
sources {
format
height
mimeType
url
width
}
originalSource {
format
height
mimeType
url
width
}
}
}
`;
2. The second problem is that one of the functionalities of the application is to manage the videos on the products, so the application must play the videos and display them in a list. However, all the links that are present in the sources list on the Video node are CDN links. (as tested with the Shopify GraphQL application installed on the store) The documentation states that the links for Video and 3D objects stored on the CDN expire, but it does not state how long it takes for the videos to expire. In this context, how can I ensure that the users of the application can always play the videos and see a thumbnail for them? How long does it take for a video to expire?
Thank you!
Solved! Go to the solution
This is an accepted solution.
Hey @nicu_taban
I've managed to perform some further tests here.
So the reason why originalSource is null and the sources array is empty, is due to the status of the Video media(docs) when you callproductCreateMedia.
At point you call productCreateMedia,
in a Video's case, the Video has not been fully transcoded. originalSource and sources will be returned when the Video Media status = READY (you'll note this when polling for the product's GID).
When Video media status is READY, it has completed the Transcoding and is ready to be displayed and those requested fields will be returned which is expected behaviour.
Hope that helps explain a little more, and please let me know if you have any questions!
Hey @nicu_taban
Thanks for raising this! With first question, Did you happen to have a request ID for that productCreateMedia mutation that I could take a look at to find out why you were getting null?
With the second question, generally our CDN cache will keep links cached for up to one year. Hope that helps!
Hello @Luke_K ,
I can reproduce the behaviour, but how do I get hold of a request ID for a particular request to Shopify? Sorry for the late response.
No worries @nicu_taban!
So if you're consistently able to replicate this, we'd return an "x-request-id" in the response header when you make a call client side against Shopify API. If you send that x-request-id through to me I'll be able to see what's happening here in the logs. Thanks!
Ok, so I replicated the issue in the request having the following request id: 0995e80e-b111-4e41-aad4-6149d29c98b8. If you could take a look, I would be grateful @Luke_K . Thank you!
Hey @nicu_taban
Thanks! That helped out alot. So, I checked out the logs for that request and the job to upload the Videos runs ok, we see the video attached to your product, all the Videos are all in the 'Ready' state (denoting that file is attached and attached to the Product).
I'm able to replicate too when directly passing in Original Source and Sources on productCreateMedia mutation, I'm returned null for originalSource and nothing in the sources array.
As an aside, I note that when I query the Product's Graphql ID (gid) directly, much like as shown here, i'm returned data for your product's originalSource and sources array. That maybe a workaround for you in the meantime whilst we look into this further.
I'll peform some further tests here regarding expected behaviour and will endeavour to be back here as soon as I have a further update.
This is an accepted solution.
Hey @nicu_taban
I've managed to perform some further tests here.
So the reason why originalSource is null and the sources array is empty, is due to the status of the Video media(docs) when you callproductCreateMedia.
At point you call productCreateMedia,
in a Video's case, the Video has not been fully transcoded. originalSource and sources will be returned when the Video Media status = READY (you'll note this when polling for the product's GID).
When Video media status is READY, it has completed the Transcoding and is ready to be displayed and those requested fields will be returned which is expected behaviour.
Hope that helps explain a little more, and please let me know if you have any questions!
Hi @Luke_K
Following your advice, I was able to obtain the links to the productMedia!
I have some final questions regarding the CDN, The productMedia object contains a sources array and an originalSrc field. Can I rely on the fact that the link in the originalSrc will not expire?
Also, the productMedia object has a preview field which is an object containing a thumbnail image, which in turn contains an originalSrc field. Can I also rely on the fact that the originalSrc for the thumbnail will not expire?
Thank you very much!
Hey @nicu_taban
Yes you can rely on those to not expire. The documentation actually needs a refresh - I believe those docs were written at a point where our Video Source URL's would expire. Hope that helps!
how can we replicate the status can you please give a little input regarding that please and I am getting the errors as yours please.
Hey @Xen-dev ,
If you're not able to retrieve the response headers in your current application, I'd recommend retrying the query in an API client like postman or insomnia. Using a tool like that can also help with debugging if the issue is specific to your app code, or the API, as well as it makes it easy to view responses.
Kind Regards,
- Kyle G.
Developer Support @ Shopify
- Was this reply helpful? Click Like to let us know!
- Was your question answered? Mark it as an Accepted Solution
- To learn more visit Shopify.dev or the Shopify Web Design and Development Blog
where do you check the status? I'm not able to log any status in my terminal and my message says invalid model 3d
Hi @Xen-dev
You can check the status of Product Media files by querying the Product object and the Media connector contained within like so:
{
product(id: "gid://shopify/Product/12345"){
id
handle
media(first: 50){
edges{
node{
id
mediaContentType
status
}
}
}
}
}
As for the Invalid 3D Model error you are seeing, please double check that you are using valid 3d files, either in GLB or USDZ format with a correct mimetype of either model/gltf-binary or model/vnd.usd+zip respectively.
If you are using the correct format and mimetypes, please do reach out to our Support Team directly through our Shopify Help Center with examples as requested in our previous thread.
I hope this helps, and I hope you have a great day 🙂
Developer Support @ Shopify
- Was this reply helpful? Click Like to let us know!
- Was your question answered? Mark it as an Accepted Solution
- To learn more visit Shopify.dev or the Shopify Web Design and Development Blog
How are you using this along with the mutation of productCreateMedia?
As given in the documentation,
the status is inside the media [] for me that is empty as I am trying to load the 3d model and it gives me an mediaUserError[] code: "INVALID" and message: Invalid model 3d
below is my code :
const fileCreateResponse = await admin!.graphql(
`#graphql
mutation createProductMedia {
productCreateMedia(productId: "gid://shopify/Product/723115xxxxx", media: [
{
originalSource: "https://d18bonzuewcc.cloudfront.net/test/77543.glb",
alt: "Comparison video showing the different models of watches.",
mediaContentType: MODEL_3D
}
]) {
media {
... fieldsForMediaTypes
mediaErrors {
code
details
message
}
mediaWarnings {
code
message
}
}
product {
id
}
mediaUserErrors {
code
field
message
}
}
}
fragment fieldsForMediaTypes on Media {
alt
mediaContentType
preview {
image {
id
}
}
status
... on Video {
id
sources {
format
height
mimeType
url
width
}
}
... on ExternalVideo {
id
host
originUrl
}
... on Model3d {
sources {
format
mimeType
url
}
boundingBox {
size {
x
y
z
}
}
}
}`,
);
console.log(fileCreateResponse, "fileCreateResponse");
const productData = await fileCreateResponse.json();
return json({
data: productData.data,
});
} catch (error: any) {
console.error("Error:", error);
return json({ error: error.message }, { status: 500 });
}
};
Hi @Xen-dev ,
It looks like you are using an external URL for the 3d model upload, which may explain the errors you're seeing. Our developer documentation does mention that the originalSource argument can only accept external URLs for Images, and Youtube/Vimeo videos. For 3D models, you will need to create a Staged Upload URL first with the stagedUploadsCreate mutation, then upload the 3D file to the staged URL following the instructions in our documentation.
If you are still experiencing errors uploading the file with a staged upload URL, please double check the file and mimetype as mentioned in our response above, and do reach out to Shopify Support via our Shopify Help Center, with specific examples and our support team can help look into actual examples further.
Developer Support @ Shopify
- Was this reply helpful? Click Like to let us know!
- Was your question answered? Mark it as an Accepted Solution
- To learn more visit Shopify.dev or the Shopify Web Design and Development Blog
Hi, Thank you for the response, I am using the stagedUploadsCreate mutation along with the productCreateMedia mutation, but still getting the same error: the status is inside the media [] for me that is empty as I am trying to load the 3d model and it gives me an mediaUserError[] code: "INVALID" and message: Invalid model 3d. It would be a great help if you could help debug. The code is given below.
export const action: ActionFunction = async ({ request }) => {
const filePath = "/Users/apple/Documents/xyz.usdz";
const fileData = fs.readFileSync(filePath);
const filename = filePath.split("/").pop();
const fileSize = fs.statSync(filePath).size.toString();
const input = [];
input.push({
filename,
mimeType: "model/vnd.usd+zip" || "model/gltf-binary",
resource: "FILE",
fileSize,
httpMethod: "POST",
});
const fileInput = {};
const stagedUploadsResponse = await admin!.graphql(
`#graphql
mutation StagedUploadsCreate($input: [StagedUploadInput!]!) {
stagedUploadsCreate(input: $input) {
stagedTargets {
url
resourceUrl
parameters {
name
value
}
}
}
}`,
{
variables: {
input: input,
},
},
);
const stagedUploadsData = await stagedUploadsResponse.json();
console.log(stagedUploadsData);
const target = stagedUploadsData.data.stagedUploadsCreate.stagedTargets[0];
const { resourceUrl, parameters, url } = target;
const formData = new FormData();
for (var param of target.parameters) {
formData.append(param.name, param.value);
}
const blob = new Blob([fileData]);
formData.append("file", blob, filename);
await axios.post(target.url, formData, {
headers: {
"Content-Type": "application/octet-stream",
},
});
console.log(resourceUrl, "resourceUrl", parameters, "parameters", url, 'url');
const fileCreateResponse = await admin!.graphql(
`#graphql
mutation productCreateMedia($media: [CreateMediaInput!]!, $productId: ID!) {
productCreateMedia(media: $media, productId: $productId) {
media {
alt
mediaContentType
status
preview {
image {
id
}
}
... on Model3d {
sources {
format
mimeType
url
}
}
}
mediaUserErrors {
code
field
message
}
product {
id
title
}
}
}`,
{
variables: {
media: [
{
alt: "3d models",
mediaContentType: "MODEL_3D",
originalSource: target.resourceUrl,
},
],
productId: "gid://shopify/Product/7231157665873",
},
},
);
console.log(fileCreateResponse, "fileCreateResponse");
const productData = await fileCreateResponse.json();
return json({
data: productData.data,
});
} catch (error: any) {
console.error("Error:", error);
return json({ error: error.message }, { status: 500 });
}
};
Hi @Xen-dev,
Thanks for confirming that you are using the Staged Upload URL now. While we're not able to help debug the code directly, the API queries you are using does look correct.
Moving forward to help investigate this issue further, we will need you to reach out to our Support Team directly with specific examples as mentioned in our previous responses. This allows us to help look into specific api calls that are returning the errors in our internal logs to help better determine the cause of this error. To do this we will need you to do the following please:
1. First confirm that the 3d files you are uploading do have the correct mimetype. For .usdz files, they need to have the model/vnd.usd+zip mimetype, and for .glb files they need to have the model/gltf-binary mimetype. Sometimes when downloading files from the internet it's possible that the file extension doesn't always match the exact file mimetype, so it's important to double check that these files have the correct mimetype to prevent this error.
2. If you've confirmed that the file types match the correct mimetype you are trying to upload, you will need to gather the following context on a recent example where the error occurred (within the last 14 days).
3. Once you have that context we need to look up the error in our logs, please reach out to our Support Team via the Shopify Help Center, logged in on the store that the error is occurring on, and we can then help look into the specific call where the error is occurring further.
I hope this helps, and I hope you have a great day 🙂
Developer Support @ Shopify
- Was this reply helpful? Click Like to let us know!
- Was your question answered? Mark it as an Accepted Solution
- To learn more visit Shopify.dev or the Shopify Web Design and Development Blog
Hi @ShopifyDevSup ,
Thank you for the response.
As you could see from the above code that the mimetypes of the files are defined as you have mentioned i.e. mimeType "model/vnd.usd+zip" and "model/gltf-binary", but still facing the error of Invalid model.
The model that is used works completely on the local server and don't understand the reason for it to not work here after following the documentation as given.
Would be great if you could help through it.
Hi @Xen-dev ,
To be able to help troubleshoot this further with you we will need to authenticate you on the store that the errors are occurring on, and look into specific examples of API calls where the error is being returned after uploading the file.
To do this you will need to reach out in our Shopify Help Center and chat with our support team directly, where we can then authenticate you as mentioned above, and help look into specific examples of the errors in our internal logs.
Please review Step 2 in the post we sent above, which outlines the what information we will need to be able to help you with this further. Once you have gathered the information in Step 2 above, please reach out in the Shopify Help Center and we can absolutely help you look into this further.
Developer Support @ Shopify
- Was this reply helpful? Click Like to let us know!
- Was your question answered? Mark it as an Accepted Solution
- To learn more visit Shopify.dev or the Shopify Web Design and Development Blog
Hi,
Thank you for the response.
I am having trouble getting the x-request-id from the request headers. I am using a proxy app and a checkout extension along with it.
It would be great if you could guide me through it.
how do you generate this request id ? as I cant find these on my logs