Manage product media with the GraphQL Admin API
You can use the GraphQL Admin API to manage the various types of media associated with merchants' products. You can also use the Storefront API to retrieve a product's media and display it on a custom storefront. This guide explains the different types of product media, and how to use Shopify's API to work with them.
Only the GraphQL Admin API and Storefront APIs support working with all types of product media. The REST Admin API supports only product images.
Required access
read_products
, write_products
access scopes.
Product media types
A product can have the following media types:
GraphQL type | Description |
---|---|
MediaImage | An image in one of the following formats: PNG, GIF, or JPG. The maximum image size is 4472 x 4472 px, or 20 megapixels. For square images, 2048 x 2048 px looks best. |
Video | An MP4 video that's hosted by Shopify. The video runtime must be 1 minute or shorter. The maximum file size is 1 GB. |
ExternalVideo | A video that's hosted on YouTube, specified by its embed URL. |
Model3d | A 3D model provided in the GLB format. The asset can be retrieved in GLB and USDZ formats. The file size must be 15 MB or smaller. |
Media actions supported by GraphQL
The GraphQL Admin API and the Storefront API both support working with all media types. The actions that you can perform on product media depend on which API you're using:
API | Upload media | Add media to a product | Retrieve media | Delete media |
---|---|---|---|---|
GraphQL Admin API | ✓ | ✓ | ✓ | ✓ |
Storefront API | - | - | ✓ | - |
Uploading media to Shopify
Before you add a piece of media to a product, you can upload it and host it on Shopify. There are two parts to uploading an asset to Shopify:
The number of media assets that a shop can upload varies, depending on the shop's Shopify plan. To learn more, see Plan-based limits for product media.
Generate the upload URL and parameters
You can use the stagedUploadsCreate mutation to generate the values that you need to authenticate multiple uploads. The mutation returns the stagedTargets
array. Each staged target has the following properties:
params
— The parameters that you use to authenticate an upload requesturl
— The signed URL where you can upload the media assetresourceUrl
— The URL that you pass to productCreateMedia in theoriginalSource
field after the asset has been uploaded
The mutation accepts an input of type stagedUploadInput, which has the following fields:
Field | Type | Description |
---|---|---|
resource | StagedUploadTargetGenerateUploadResource | Specifies the resource type to upload. Valid media values: VIDEO , MODEL_3D , IMAGE . |
filename | String | The name of the file to upload. |
mimeType | String |
The Media type of the file to upload. Use one of the following values:
|
httpMethod | StagedUploadHttpMethodType | The HTTP method to be used by the staged upload. Valid values: POST (all media types), PUT (images only). |
fileSize | UnsignedInt64 | The size in bytes of the file to upload. |
The following example uses the stagedUploadsCreate mutation to generate the upload values required to upload an MP4 video and a 3D model.
POST /admin/api/2020-01/graphql.json
mutation generateStagedUploads($input: [StagedUploadInput!]!) {
stagedUploadsCreate(input: $input) {
stagedTargets {
url
resourceUrl
parameters {
name
value
}
}
mediaUserErrors { code, field, message }
}
}
Variables
{
"input" : [
{
"filename": "watches_comparison.mp4",
"mimeType": "video/mp4",
"resource": "VIDEO",
"fileSize": "899765"
},
{
"filename": "another_watch.glb",
"mimeType": "model/gltf-binary",
"resource": "MODEL_3D",
"fileSize": "456"
}
]
}
JSON response
{
"data": {
"stagedUploadsCreate": {
"stagedTargets": [
{
"url": "https://shopify-video-production-core-originals.s3.amazonaws.com/",
"resourceUrl": "https://shopify-video-production-core-originals.s3.amazonaws.com?external_video_id=4635",
"parameters": [
{
"name": "bucket",
"value": "shopify-video-production-core-originals"
},
{
"name": "key",
"value": "c/o/v/7827ebe111a24a09869ec90f3412768f.mp4"
},
{
"name": "policy",
"value": "eyJjb25kaXRpb25zIjpbWyJlcSIsIiRidWNrZXQiLCJzaG9waWZ5LXZpZGVvLXByb2R1Y3Rpb24tY29yZS1vcmlnaW5hbHMiXSxbImVxIiwiJGtleSIsImMvby92Lzc4MjdlYmUxMTFhMjRhMDk4NjllYzkwZjM0MTI3NjhmLm1wNCJdLFsiY29udGVudC1sZW5ndGgtcmFuZ2UiLDg5OTc2NSw4OTk3NjVdLFsiZXEiLCIkY2FjaGUtY29udHJvbCIsInB1YmxpYywgbWF4LWFnZT0zMTUzNjAwMCJdLFsiZXEiLCIkeC1hbXotY3JlZGVudGlhbCIsIkFLSUFZT0k1S1o2MkpRQ1c2M0xVLzIwMTkwOTE3L3VzLWVhc3QtMS9zMy9hd3M0X3JlcXVlc3QiXSxbImVxIiwiJHgtYW16LWFsZ29yaXRobSIsIkFXUzQtSE1BQy1TSEEyNTYiXSxbImVxIiwiJHgtYW16LWRhdGUiLCIyMDE5MDkxN1QyMDQ4MzhaIl1dLCJleHBpcmF0aW9uIjoiMjAxOS0wOS0xN1QyMTo0ODozOFoifQ=="
},
{
"name": "cache-control",
"value": "public, max-age=31536000"
},
{
"name": "x-amz-signature",
"value": "16bd494f09d3739f428777e44b2a1f8de96f9545b83db4a58cf027503833c9fc"
},
{
"name": "x-amz-credential",
"value": "AKIAYOI5KZ62JQCW63LU/20190917/us-east-1/s3/aws4_request"
},
{
"name": "x-amz-algorithm",
"value": "AWS4-HMAC-SHA256"
},
{
"name": "x-amz-date",
"value": "20190917T204838Z"
}
]
},
{
"url": "https://storage.googleapis.com/threed-models-production/models/60d55fd9e8cba091/another_watch.glb?external_model3d_id=bW9kZWwzZC0xNTk3",
"resourceUrl": "https://storage.googleapis.com/threed-models-production/models/60d55fd9e8cba091/another_watch.glb?external_model3d_id=bW9kZWwzZC0xNTk3",
"parameters": [
{
"name": "GoogleAccessId",
"value": "threed-model-service--6bgx7cbe@shopify-applications.iam.gserviceaccount.com"
},
{
"name": "key",
"value": "models/60d55fd9e8cba091/another_watch.glb"
},
{
"name": "policy",
"value": "eyJleHBpcmF0aW9uIjoiMjAxOS0wOS0xN1QyMTowMzozOFoiLCJjb25kaXRpb25zIjpbWyJlcSIsIiRidWNrZXQiLCJ0aHJlZWQtbW9kZWxzLXByb2R1Y3Rpb24iXSxbImVxIiwiJGtleSIsIm1vZGVscy82MGQ1NWZkOWU4Y2JhMDkxL2Fub3RoZXJfd2F0Y2guZ2xiIl0sWyJjb250ZW50LWxlbmd0aC1yYW5nZSIsNDU2LDQ1Nl1dfQ=="
},
{
"name": "signature",
"value": "Tp5B5OXpE2bWEnvQ7F1JvozQPGloujwMJ9CMbLWUwM1LmnUpL3tAh6eJ9d61laiwLqGRgTUOdTkMZTofFi5n/af9sCDZAHXeDKzukrohWYX8M+Ui7VvDflA/l0CIv5uzhMgbfRmqK9XCBK61/WkSuMZblEP0AAmqMtkKZhuxTzj8JMH83JYRqVb0r9k9IE+TEJluc5eSJ6Y+NrrGFV+nbh7T/lexCc02v8NVNNw2I0xNyQ8sZWW53c0WJIuQ4F+oagMGSXeyhxNvjKVPC1NVv5NqFM4A/d7eGLwBmlz4lE4GyUonxyH//Iww7zq+Klyr0+mP8IcYnGJQpT3uJDow+w=="
}
]
}
],
"mediaUserErrors": []
}
}
}
Upload the asset
After generating the parameters and URL for an upload, you need to upload the asset by using a POST or PUT request. The request is formatted differently depending on the media type and the HTTP method that you're using.
Videos, 3D models: POST request
Use a multipart form, and include all parameters as form inputs in the request body:
curl -v \
-F "bucket=shopify-video-production-core-originals" \
-F "key=c/o/v/7827ebe111a24a09869ec90f3412768f.mp4" \
-F "policy=eyJjb25kaXRpb25zIjpbWyJlcSIsIiRidWNrZXQiLCJzaG9waWZ5LXZpZGVvLXByb2R1Y3Rpb24tY29yZS1vcmlnaW5hbHMiXSxbImVxIiwiJGtleSIsImMvby92Lzc4MjdlYmUxMTFhMjRhMDk4NjllYzkwZjM0MTI3NjhmLm1wNCJdLFsiY29udGVudC1sZW5ndGgtcmFuZ2UiLDg5OTc2NSw4OTk3NjVdLFsiZXEiLCIkY2FjaGUtY29udHJvbCIsInB1YmxpYywgbWF4LWFnZT0zMTUzNjAwMCJdLFsiZXEiLCIkeC1hbXotY3JlZGVudGlhbCIsIkFLSUFZT0k1S1o2MkpRQ1c2M0xVLzIwMTkwOTE3L3VzLWVhc3QtMS9zMy9hd3M0X3JlcXVlc3QiXSxbImVxIiwiJHgtYW16LWFsZ29yaXRobSIsIkFXUzQtSE1BQy1TSEEyNTYiXSxbImVxIiwiJHgtYW16LWRhdGUiLCIyMDE5MDkxN1QyMDQ4MzhaIl1dLCJleHBpcmF0aW9uIjoiMjAxOS0wOS0xN1QyMTo0ODozOFoifQ==" \
-F "cache-control=public, max-age=31536000" \
-F "x-amz-signature=16bd494f09d3739f428777e44b2a1f8de96f9545b83db4a58cf027503833c9fc" \
-F "x-amz-algorithm=AWS4-HMAC-SHA256" \
-F "x-amz-date=20190917T204838Z"
-F "file=@/Users/shopifyemployee/watches_comparison.mp4" \
"https://shopify-video-production-core-originals.s3.amazonaws.com/"
Images: POST request
Use a multipart form, and include all parameters as form inputs in the request body:
curl -v \
-F "AWSAccessKeyId=AKIAJYM555KVYEWGJDKQ" \
-F "key=tmp/17681717/products/ec24e5f6-ba91-43d7-bb43-4c7174770ac1/watches_comparison.png" \
-F "policy=eyJleHBpcmF0aW9uIjoiMjAxOS0wNi0xNFQxNjo0OTowM1oiLCJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJzaG9waWZ5In0seyJrZXkiOiJ0bXBcLzE3NjgxNzE3XC9wcm9kdWN0c1wvZWMyNGU1ZjYtYmE5MS00M2Q3LWJiNDMtNGM3MTc0NzcwYWMxXC9JR1EucG5nIn0seyJGaWxlbmFtZSI6IklHUS5wbmcifSx7Ik1pbWUtVHlwZSI6ImltYWdlXC9wbmcifSx7IkZpbGUtU2l6ZSI6IjMzNjQ2MSJ9LHsic3VjY2Vzc19hY3Rpb25fc3RhdHVzIjoiMjAxIn0seyJhY2wiOiJwcml2YXRlIn0sWyJjb250ZW50LWxlbmd0aC1yYW5nZSIsMSwyMDk3MTUyMF1dfQ==" \
-F "signature=yZsEky4DDqbbcNnOVFNAagZnlcI=" \
-F "Filename=watches_comparison.png" \
-F "Mime-Type=image/png" \
-F "File-Size=336461" \
-F "success_action_status=201" \
-F "acl=private" \
-F "file=@/Users/shopifyemployee/Desktop/watches_comparison.png" \
"https://shopify.s3.amazonaws.com/"
Images: PUT request
Include the parameters as request headers. Additional parameters are already included in the URL:
curl -X PUT -T ~/Desktop/watches_comparison.png \
-H 'content_type:image/png' \
-H 'x-aws-acl:private' \
-H 'Content-Type:image/png' \
"https://shopify.s3.amazonaws.com/tmp/17681717/products/35c29fff-a86e-4ca4-a8aa-f725b2b6fdcb/watches_comparison.png?AWSAccessKeyId=AKIAJYM555KVYEWGJDKQ&Expires=1560534810&Signature=zUu%2BAmDsgkUsMhE%2BixRwRdXCNMY%3D"
Plan-based limits for product media
There is a limit to the number of Shopify-hosted videos and 3D models that a store can have. The limit depends on the Shopify plan that the store is on:
Basic Shopify | Shopify | Advanced Shopify | Shopify Plus |
---|---|---|---|
250 | 1000 | 5000 | Contact Plus Support |
Adding media to a product
You can add new media to a product by using the productCreateMedia mutation. The mutation takes two arguments:
- productId — The ID of the product that you're adding media to.
- media — An array of CreateMediaInput objects.
For each CreateMediaInput object, include the following fields:
Field | Type | Description |
---|---|---|
originalSource | String | The original source of the media object. Can be an external URL for images and YouTube videos, or a signed upload URL for images, videos, and 3D models hosted by Shopify. For assets hosted by Shopify, use the `resourceUrl` value returned by the `stagedUploadsCreate` mutation. |
alt | String | The alt text associated to the media. |
mediaContentType | MediaContentType | The content type of the asset that you're adding. Valid values: IMAGE , VIDEO , EXTERNAL_VIDEO , MODEL_3D . |
The following example adds a Shopify-hosted video to a product.
POST /admin/api/2020-01/graphql.json
mutation createProductMedia(
$id: ID!
$media: [CreateMediaInput!]!
) {
productCreateMedia(productId: $id, media: $media) {
media {
... fieldsForMediaTypes
mediaErrors {
code
details
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
embeddedUrl
}
... on Model3d {
sources {
format
mimeType
url
}
}
}
Variables
{
"id": "gid://shopify/Product/1",
"media": [
{
"originalSource": "https://storage.googleapis.com/shopify-video-production-core-originals/c/o/v/af64d230f6bc40cbba40a87be950a1a2.mp4?external_video_id=1730",
"alt": "Comparison video showing the different models of watches.",
"mediaContentType": "VIDEO"
}
]
}
JSON response
{
"data": {
"productCreateMedia": {
"media": [
{
"mediaContentType": "VIDEO",
"alt": "Comparison video showing the different models of watches.",
"status": "UPLOADED",
"id": "gid://shopify/Video/3113016",
"sources": [
{
"format": "mp4",
"height": 360,
"mimeType": "video/mp4",
"url": "https://videos.shopifycdn.com/c/vp/2a82811738ca41e7a508e6744028d169/SD-360p.mp4?Expires=1560534951&KeyName=core-signing-key-1&Signature=T_aftXCRfamwlXZNlCwSAOq1sg8=",
"width": 640
}
]
}
],
"mediaUserErrors": []
}
}
}
Retrieving media objects
You can retrieve a product's media of all types by using the media
connection on the Product type. The connection returns nodes that implement the Media interface.
The media
connection includes the mediaContentType
field, which you can use to check the media type of each node. Because each media type can return different fields, you can specify the return fields for each type by using fragments:
POST /admin/api/2020-01/graphql.json
{
product(id:"gid://shopify/Product/1") {
title
media(first:5) {
edges {
node {
... fieldsForMediaTypes
}
}
}
}
}
fragment fieldsForMediaTypes on Media {
alt
mediaContentType
preview {
image {
id
altText
originalSrc
}
}
status
... on Video {
id
sources {
format
height
mimeType
url
width
}
originalSource {
format
height
mimeType
url
width
}
}
... on ExternalVideo {
id
embeddedUrl
}
... on Model3d {
sources {
format
mimeType
url
}
originalSource {
format
mimeType
url
}
}
... on MediaImage {
id
image {
altText
originalSrc
}
}
}
JSON response
{
"data": {
"product": {
"title": "Polaris Watch",
"media": {
"edges": [
{
"node": {
"alt": "Comparison video showing the different models of watches.",
"mediaContentType": "VIDEO",
"preview": {
"image": {
"id": "gid://shopify/Image/1",
"altText": "Comparison video showing the different models of watches..",
"originalSrc": "https://cdn.shopify.com/s/files/1/1768/1717/products/31f1438669864f4f91847f07e39e3835.jpg?v=1234567890"
}
},
"status": "READY",
"id": "gid://shopify/Video/3113016",
"sources": [
{
"format": "mp4",
"height": 360,
"mimeType": "video/mp4",
"url": "https://videos.shopifycdn.com/c/vp/2a82811738ca41e7a508e6744028d169/SD-360p.mp4?Expires=1560956269&KeyName=core-signing-key-1&Signature=MYq_eEWGB-2Ww-oN58j-TbxwDYw=",
"width": 640
}
]
}
},
{
"node": {
"alt": "Comparison video showing the different models of watches.",
"mediaContentType": "EXTERNAL_VIDEO",
"preview": {
"image" : {
"id": "gid://shopify/Image/2",
"altText": "Comparison video showing the different models of watches..",
"originalSrc": "https://cdn.shopify.com/s/files/1/1768/1717/products/31f1438669864f4f91847f07e39e3835.jpg?v=2234567890"
}
},
"status": "READY",
"id": "gid://shopify/ExternalVideo/8486968",
"embeddedUrl": "https://youtu.be/z7RLjNOael0"
}
}
]
}
}
}
}
Checking whether media is ready to display
When you add a piece of media to a product, Shopify needs to process the media before it can be displayed. The Media interface includes the status
field, which you can use to check whether the media has been processed. The status
field can return the following values:
Value | Description |
---|---|
UPLOADED | The media has been uploaded but not processed. |
PROCESSING | The media is being processed. |
READY | The media is ready to be displayed. |
FAILED | The media processing failed. |
Updating product media
You can use the productUpdateMedia mutation to update a piece of media associated with a product. As part of the mutation input, include the following arguments:
productId
- The ID of the product that the media belongs to.media
- An array of media changes to apply.
You can change a media item's alt text or preview image URL. Include the media's ID to identify the media you want to update.
Identify the media to update by its ID. For example, the JSON variable below updates the alt text of the MediaImage with ID gid://shopify/MediaImage/42729528
:
{
"media": [
{
"id": "gid://shopify/MediaImage/42729528",
"alt": "Some new alt text."
}
]
}
The example below shows how to use the productUpdateMedia mutation to update the alt text of a piece of media. The example uses a JSON variable to provide the media changes, and a fragment to select the return fields based on the media type.
POST /admin/api/2020-01/graphql.json
mutation updateProductMedia($mediaUpdates:[UpdateMediaInput!]!) {
productUpdateMedia(
productId: "gid://shopify/Product/10079786124",
media: $mediaUpdates
) {
media {
alt
mediaContentType
... mediaFieldsByType
}
mediaUserErrors {
field
message
}
}
}
fragment mediaFieldsByType on Media {
...on ExternalVideo {
id
embeddedUrl
}
...on MediaImage {
id
image {
originalSrc
}
}
...on Model3d {
id
sources {
url
mimeType
format
filesize
}
}
...on Video {
id
sources {
url
mimeType
format
height
width
}
}
}
Variables
{
"mediaUpdates": [
{
"id": "gid://shopify/MediaImage/42729528",
"alt": "Man wearing Polaris watch."
}
]
}
JSON response
{
"data": {
"productUpdateMedia": {
"media": [
{
"alt": "Man wearing Polaris watch.",
"mediaContentType": "IMAGE",
"id": "gid://shopify/MediaImage/42729528",
"image": {
"originalSrc": "https://cdn.shopify.com/s/files/1/1768/1717/products/StockSnap_9NPZZJCWW3_copy.jpg?v=1566862515"
}
}
],
"mediaUserErrors": []
}
}
}
Reordering media objects
You can reorder a product’s media by using the productReorderMedia mutation. The mutation accepts two arguments:
- id — The ID of the product whose media you’re reordering.
moves — An array of tuples consisting of a media object’s ID and its new position in the list. For example, the following array would move the media objects to the front of the product’s media list:
[ { "id": "gid://shopify/MediaImage/37" "newPosition": "0" }, { "id": "gid://shopify/Video/2", "newPosition": "1" } ]
The following example adjusts the order of a products first three media assets.
POST /admin/api/2020-01/graphql.json
mutation reorderProductMedia($id:ID!, $moves: [MoveInput!]!) {
productReorderMedia(id: $id, moves: $moves) {
job {
id
done
}
mediaUserErrors {
code
field
message
}
}
}
Variables
{
"id": "gid://shopify/Product/1",
"moves": [
{
"id": "gid://shopify/MediaImage/37",
"newPosition": "0"
}, {
"id": "gid://shopify/Video/2",
"newPosition": "1"
}, {
"id": "gid://shopify/ExternalVideo/1",
"newPosition": "2"
}
]
}
JSON response
{
"data": {
"productReorderMedia": {
"job": {
"id": "gid://shopify/Job/e1104bd6-4234-4b3e-b6d6-173c1d7e89f5",
"done": false
},
"mediaUserErrors": []
}
}
}
You can use the returned ID for the job to poll for when the reordering job is complete.
Deleting media objects
To delete media assets from a product, use the productDeleteMedia mutation. The mutation accepts two arguments:
- productId — The ID of the product whose media you’re deleting.
- mediaIds — An array of IDs for the media that you’re deleting.
The following example deletes two media assets from a product.
POST /admin/api/2020-01/graphql.json
mutation deleteProductMedia($id: ID!, $mediaIds: [ID!]!) {
productDeleteMedia(productId: $id, mediaIds: $mediaIds) {
deletedMediaIds
product {
id
}
mediaUserErrors {
code
field
message
}
}
}
Variables
{
"id": "gid://shopify/Product/1",
"mediaIds": [
"gid://shopify/ExternalVideo/1",
"gid://shopify/Video/2"
]
}
JSON response
{
"data": {
"productDeleteMedia": {
"deletedMediaIds": [
"gid://shopify/ExternalVideo/1",
"gid://shopify/Video/2"
],
"product": {
"id": "gid://shopify/Product/1"
},
"mediaUserErrors": []
}
}
}
Retrieve product media by using the Storefront API
With the Storefront API, use the media
field on the Product type to query for a product's media. Use a fragment to specify the fields that you want to return for each possible media type.
POST /api/2020-01/graphql.json
{
node(id: "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0LzEwMDc5Nzg3NTMy") {
...on Product {
id
media(first: 10) {
edges {
node {
mediaContentType
alt
...mediaFieldsByType
}
}
}
}
}
}
fragment mediaFieldsByType on Media {
...on ExternalVideo {
id
embeddedUrl
}
...on MediaImage {
image {
originalSrc
}
}
...on Model3d {
sources {
url
mimeType
format
filesize
}
}
...on Video {
sources {
url
mimeType
format
height
width
}
}
}
JSON response
{
"data": {
"node": {
"id": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0LzEwMDc5Nzg3NTMy",
"media": {
"edges": [
{
"node": {
"mediaContentType": "VIDEO",
"alt": "Comparison video showing the different models of watches.",
"sources": [
{
"url": "https://videos.shopifycdn.com/c/vp/2a82811738ca41e7a508e6744028d169/SD-360p.mp4?Expires=1575744400&KeyName=core-signing-key-1&Signature=OPKELzhY-kYTx9QH9x6NJA9IqnI=",
"mimeType": "video/mp4",
"format": "mp4",
"height": 360,
"width": 640
}
]
}
},
{
"node": {
"mediaContentType": "IMAGE",
"alt": "Polaris watch",
"image": {
"originalSrc": "https://cdn.shopify.com/s/files/1/1768/1717/products/IGQ.png?v=1560528103"
}
}
}
]
}
}
}
}