Skip to main content

Manage media for products and collections

Use the GraphQL Admin API to manage media for products and collections. The API supports images, Shopify-hosted videos, external videos (YouTube and Vimeo), and 3D models.



The GraphQL Admin API manages files independently from products using a unified file system. Upload a file once to Shopify's CDN using fileCreate or stagedUploadsCreate, get a unique file ID, then reference that ID from multiple products, variants, collections, or themes. Updates to the file propagate everywhere it's used.

Files are processed asynchronously. You poll the fileStatus field until it's READY, then associate the file with products using the file ID. This separation allows file reuse across resources and prevents duplicate uploads. For example, one product image can be referenced by multiple products without uploading it multiple times.

Anchor to Supported media typesSupported media types

Media typeFormatsSize limitsKey details
MediaImagePNG, GIF, JPEG, WEBP, HEICMax 20 MB
Max 4472 x 4472 px (20 MP)
Recommended: 2048 x 2048 px
Shopify generates multiple sizes automatically for responsive display.
Aspect ratio: 100:1 to 1:100
VideoMP4, MOV, WEBMMax 1 GB
Max 10 minutes
Max 3840x2160 (UHD/4K)
Shopify generates multiple resolutions (480p, 720p, 1080p) and formats (HLS, MP4).
Apps can create up to 1,000 videos for each store in a week.
ExternalVideoYouTube, VimeoNo limits (hosted externally)No file upload required. Provide the embed or share URL.
Doesn't count against shop's storage quota.
Model3dGLB, USDZMax 500 MBFor augmented reality on supported devices.
Shopify serves models in both GLB (web) and USDZ (iOS AR) formats.

Anchor to Step 1: Upload files to ShopifyStep 1: Upload files to Shopify

Before you can display media to customers, files must be hosted on Shopify's CDN. Shopify processes files asynchronously, generating multiple sizes for images, transcoding videos to multiple resolutions, and optimizing 3D models. Files are then served globally through a fast CDN.

The GraphQL Admin API provides two approaches for uploading files:

ApproachBest forHow it works
fileCreateSimple uploads from accessible URLsProvide a URL and Shopify downloads, processes, and stores the file.
stagedUploadsCreateLarge files, files on your server, or when you need more controlGet secure upload credentials, upload directly to Shopify's storage, then register the file.

Anchor to Option A: Upload from a URL with ,[object Object]Option A: Upload from a URL with fileCreate

Use fileCreate when your files are already accessible at a public URL. This is the simplest approach: you provide a URL and Shopify handles downloading, processing, and storing the file.

The following example uploads an image from an external URL:

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL mutation

mutation CreateFile {
fileCreate(files: [{
# The URL where Shopify will download the file from.
originalSource: "https://example.com/image.jpg",
alt: "Product image",
# Specify the media type: IMAGE, VIDEO, or MODEL_3D.
contentType: IMAGE
}]) {
files {
id
fileStatus
alt
}
userErrors {
field
message
}
}
}

JSON response

{
"data": {
"fileCreate": {
"files": [
{
"id": "gid://shopify/MediaImage/25823372804185",
"fileStatus": "PROCESSING",
"alt": "Product image"
}
],
"userErrors": []
}
}
}

Anchor to Option B: Upload directly with ,[object Object]Option B: Upload directly with stagedUploadsCreate

Use stagedUploadsCreate when you need more control over the upload process. This two-step approach is recommended for:

  • Large files: Videos, 3D models, and high-resolution images benefit from direct uploads.
  • Files on your server: Files that aren't accessible via public URL.
  • Unreliable networks: Direct uploads handle interruptions more gracefully.
  • Bulk imports: Uploading many files efficiently.

Anchor to 1. Generate upload credentials1. Generate upload credentials

Generate secure upload credentials for your files. You must specify the fileSize for videos and 3D models:

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL mutation

mutation generateStagedUploads {
stagedUploadsCreate(input: [
{
filename: "watches_comparison.mp4",
mimeType: "video/mp4",
resource: VIDEO,
# fileSize is required for videos and 3D models.
fileSize: "899765"
},
{
filename: "watch_model.glb",
mimeType: "model/gltf-binary",
resource: MODEL_3D,
fileSize: "456000"
}
]) {
stagedTargets {
url
resourceUrl
parameters {
name
value
}
}
userErrors {
field
message
}
}
}

JSON response

{
"data": {
"stagedUploadsCreate": {
"stagedTargets": [
{
"url": "https://shopify-video-production-core-originals.storage.googleapis.com",
"resourceUrl": "https://shopify-video-production-core-originals.storage.googleapis.com?external_video_id=8490719",
"parameters": [
{
"name": "GoogleAccessId",
"value": "video-production@video-production-225115.iam.gserviceaccount.com"
},
{
"name": "key",
"value": "c/o/v/2e285673bb044aa1a174f813a2e953cf.mp4"
}
]
},
{
"url": "https://storage.googleapis.com/threed-models-production/models/1495b0cb3bcee78e/watch_model.glb",
"resourceUrl": "https://storage.googleapis.com/threed-models-production/models/1495b0cb3bcee78e/watch_model.glb?external_model3d_id=bW9kZWwzZC0yNjIzMTA=",
"parameters": [
{
"name": "GoogleAccessId",
"value": "threed-model-service-prod@threed-model-service.iam.gserviceaccount.com"
},
{
"name": "key",
"value": "models/1495b0cb3bcee78e/watch_model.glb"
}
]
}
],
"userErrors": []
}
}
}

Upload the file to the url using the provided parameters. The request format differs by media type.

Videos and 3D models use a POST request with multipart form data:

curl -v \
-F "GoogleAccessId=video-production@video-production-225115.iam.gserviceaccount.com" \
-F "key=c/o/v/123bbb4321f4d40a101mi1fd3c32aa7.mp4" \
-F "policy=eyJjb25kaXRpb25zIjpbWyJlcSIsIiRidWNrZXQiLCJzaG9waWZ5..." \
-F "signature=vD7N/vHO4MS0EpG..." \
-F "file=@/path/to/watches_comparison.mp4" \
"https://shopify-video-production-core-originals.storage.googleapis.com"

Images use a PUT request with parameters as headers:

curl -X PUT -T /path/to/image.png \
-H 'content_type:image/png' \
-H 'acl:private' \
"https://shopify-staged-uploads.storage.googleapis.com/tmp/45732462614/products/4da01e43-76ff-4014-be67-754bc154494d/image.png?X-Goog-Algorithm=GOOG4-RSA-SHA256&..."
Tip

You can send requests with form data using API clients like Postman or Insomnia.

Anchor to 3. Register the uploaded file3. Register the uploaded file

After uploading, register the file with Shopify using fileCreate. Use the resourceUrl from the staged upload response as the originalSource:

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL mutation

mutation CreateFileFromStagedUpload {
fileCreate(files: [{
# Use the resourceUrl from the stagedUploadsCreate response.
originalSource: "https://shopify-video-production-core-originals.storage.googleapis.com?external_video_id=8490719",
alt: "Product comparison video",
contentType: VIDEO
}]) {
files {
id
fileStatus
alt
}
userErrors {
field
message
}
}
}

JSON response

{
"data": {
"fileCreate": {
"files": [
{
"id": "gid://shopify/Video/25823372804185",
"fileStatus": "PROCESSING",
"alt": "Product comparison video"
}
],
"userErrors": []
}
}
}

Anchor to Step 2: Poll for file readinessStep 2: Poll for file readiness

Regardless of which upload method you use, files are processed asynchronously. Poll the fileStatus field until it becomes READY before associating the file with products:

  • UPLOADED: File has been received by Shopify's servers.
  • PROCESSING: File is being processed (resized, transcoded, and so on).
  • READY: File is ready to associate with products.
  • FAILED: Processing failed. Check error details.

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL query

query CheckFileStatus {
# Use the file ID returned from fileCreate.
node(id: "gid://shopify/MediaImage/25823372804185") {
... on File {
# Poll until fileStatus is READY.
fileStatus
preview {
image {
url
}
}
}
}
}

JSON response (when READY)

{
"data": {
"node": {
"fileStatus": "READY",
"preview": {
"image": {
"url": "https://cdn.shopify.com/s/files/1/0574/7248/3417/files/image.jpg"
}
}
}
}
}

Anchor to Step 3: Add media to productsStep 3: Add media to products

After a file's status is READY, you can associate it with products to display the media to customers. This association determines which media appears on product pages, in collection listings, and across sales channels.

Use productSet, productCreate, or productUpdate with the media field. Reference the file by its ID in the originalSource field.

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL mutation

mutation AssociateMediaWithProduct {
productSet(input: {
id: "gid://shopify/Product/7882412064857",
files: [
{
# Reference the file by its ID (not a URL).
originalSource: "gid://shopify/MediaImage/25823372804185",
alt: "Product image",
contentType: IMAGE
}
]
}) {
product {
id
media(first: 10) {
nodes {
alt
mediaContentType
... on MediaImage {
image {
url
}
}
}
}
}
userErrors {
field
message
}
}
}

JSON response

{
"data": {
"productSet": {
"product": {
"id": "gid://shopify/Product/7882412064857",
"media": {
"nodes": [
{
"alt": "Product image",
"mediaContentType": "IMAGE",
"image": {
"url": "https://cdn.shopify.com/s/files/1/0574/7248/3417/files/image.jpg"
}
}
]
}
},
"userErrors": []
}
}
}

Anchor to Step 4: Add media to product variantsStep 4: Add media to product variants

You can add media to variants when creating products by using the mediaSrc field in productCreate. The mediaSrc value must match one of the media originalSource fields on the product.

The following example shows how to add media when creating a product with variants:

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL mutation

mutation CreateProductWithVariantMedia {
productCreate(
# Define the media files for the product.
media: [
{
mediaContentType: IMAGE,
originalSource: "https://example.com/red_t_shirt.jpg"
},
{
mediaContentType: IMAGE,
originalSource: "https://example.com/yellow_t_shirt.jpg"
}
],
product: {
title: "T-shirt",
productOptions: [
{
name: "Color",
values: [{ name: "Red" }, { name: "Yellow" }]
},
{
name: "Size",
values: [{ name: "M" }, { name: "L" }]
}
],
variants: [
{
# mediaSrc must match an originalSource value above.
mediaSrc: ["https://example.com/red_t_shirt.jpg"],
optionValues: [
{ optionName: "Color", name: "Red" },
{ optionName: "Size", name: "M" }
]
},
{
mediaSrc: ["https://example.com/red_t_shirt.jpg"],
optionValues: [
{ optionName: "Color", name: "Red" },
{ optionName: "Size", name: "L" }
]
},
{
mediaSrc: ["https://example.com/yellow_t_shirt.jpg"],
optionValues: [
{ optionName: "Color", name: "Yellow" },
{ optionName: "Size", name: "M" }
]
},
{
mediaSrc: ["https://example.com/yellow_t_shirt.jpg"],
optionValues: [
{ optionName: "Color", name: "Yellow" },
{ optionName: "Size", name: "L" }
]
}
]
}
) {
product {
id
variants(first: 10) {
edges {
node {
selectedOptions {
name
value
}
media(first: 10) {
edges {
node {
... on MediaImage {
id
alt
image {
url
}
}
}
}
}
}
}
}
}
userErrors {
field
message
}
}
}

JSON response

{
"data": {
"productCreate": {
"product": {
"id": "gid://shopify/Product/123456789",
"variants": {
"edges": [
{
"node": {
"selectedOptions": [
{ "name": "Color", "value": "Red" },
{ "name": "Size", "value": "M" }
],
"media": {
"edges": [
{
"node": {
"id": "gid://shopify/MediaImage/1",
"alt": null,
"image": {
"url": "https://cdn.shopify.com/s/files/1/0574/7248/3417/files/red_t_shirt.jpg"
}
}
}
]
}
}
},
{
"node": {
"selectedOptions": [
{ "name": "Color", "value": "Red" },
{ "name": "Size", "value": "L" }
],
"media": {
"edges": [
{
"node": {
"id": "gid://shopify/MediaImage/1",
"alt": null,
"image": {
"url": "https://cdn.shopify.com/s/files/1/0574/7248/3417/files/red_t_shirt.jpg"
}
}
}
]
}
}
}
]
}
},
"userErrors": []
}
}
}

Anchor to Step 5: Add images to collectionsStep 5: Add images to collections

Collection images appear in collection listings, navigation menus, and promotional banners. Unlike products, collections use image URLs directly instead of file ID references.

To add images to collections, use collectionCreate or collectionUpdate with image URLs. You can use Shopify CDN URLs from uploaded files or external URLs from your own hosting.

Anchor to Option A: Use Shopify CDN filesOption A: Use Shopify CDN files

To add a file from Shopify's CDN to a collection, first upload it with fileCreate, get the CDN URL from the file, then use that URL in the collection mutation.

First, query the file to get its CDN URL:

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL query

query GetFileCDNUrl {
# Use the file ID from fileCreate.
node(id: "gid://shopify/MediaImage/25823372836953") {
id
... on MediaImage {
preview {
status
image {
# Use this URL in the collection mutation.
url
}
}
}
}
}

JSON response

{
"data": {
"node": {
"id": "gid://shopify/MediaImage/25823372836953",
"preview": {
"status": "READY",
"image": {
"url": "https://cdn.shopify.com/s/files/1/0574/7248/3417/files/myfilename.jpg"
}
}
}
}
}

Then create the collection with the image URL:

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL mutation

mutation CreateCollectionWithImage {
collectionCreate(input: {
title: "My Collection"
image: {
# Use the CDN URL from the GetFileCDNUrl query.
src: "https://cdn.shopify.com/s/files/1/0574/7248/3417/files/myfilename.jpg"
altText: "Collection image"
}
}) {
collection {
id
image {
url
altText
}
}
userErrors {
field
message
}
}
}

JSON response

{
"data": {
"collectionCreate": {
"collection": {
"id": "gid://shopify/Collection/123",
"image": {
"url": "https://cdn.shopify.com/s/files/1/0574/7248/3417/files/myfilename.jpg",
"altText": "Collection image"
}
},
"userErrors": []
}
}
}

Anchor to Option B: Use external image URLsOption B: Use external image URLs

You can also provide external URLs directly without uploading to Shopify. This is useful when you host images on your own CDN or need to update images frequently without re-uploading.

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL mutation

mutation CreateCollectionWithExternalImage {
collectionCreate(input: {
title: "My Collection"
image: {
src: "https://example.com/collection-image.jpg"
altText: "Collection image"
}
}) {
collection {
id
image {
url
altText
}
}
userErrors {
field
message
}
}
}

JSON response

{
"data": {
"collectionCreate": {
"collection": {
"id": "gid://shopify/Collection/456",
"image": {
"url": "https://example.com/collection-image.jpg",
"altText": "Collection image"
}
},
"userErrors": []
}
}
}

Anchor to Step 6: Update and reorder mediaStep 6: Update and reorder media

After adding media to products, you might need to update which products reference a file or change the display order.

Anchor to Update file associationsUpdate file associations

The unified file system's key advantage is file reuse. When you have a logo, brand image, or shared product shot, you can use the same file across multiple products without uploading duplicates. Use fileUpdate to manage which products reference a file.

The following example shows how to move a file from one product to another:

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL mutation

mutation UpdateFileAssociations {
fileUpdate(files: [{
# The file ID to update associations for.
id: "gid://shopify/MediaImage/1234567890",
# Remove the file from this product.
referencesToRemove: ["gid://shopify/Product/1234567890"],
# Add the file to this product.
referencesToAdd: ["gid://shopify/Product/9876543210"]
}]) {
files {
id
}
userErrors {
field
message
}
}
}

JSON response

{
"data": {
"fileUpdate": {
"files": [
{
"id": "gid://shopify/MediaImage/1234567890"
}
],
"userErrors": []
}
}
}

Anchor to Reorder product mediaReorder product media

The first media item in a product typically serves as the primary image in collection pages, search results, and product thumbnails. Use productReorderMedia to change media order without deleting and re-adding items.

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL mutation

mutation reorderProductMedia {
productReorderMedia(
id: "gid://shopify/Product/1",
# Specify new positions for the media you're moving.
moves: [
{
id: "gid://shopify/MediaImage/37",
# Position 0 becomes the primary image.
newPosition: "0"
},
{
id: "gid://shopify/Video/2",
newPosition: "1"
},
{
id: "gid://shopify/ExternalVideo/1",
newPosition: "2"
}
]) {
job {
id
done
}
mediaUserErrors {
code
field
message
}
}
}

JSON response

{
"data": {
"productReorderMedia": {
"job": {
"id": "gid://shopify/Job/17366d70-740a-4048-a102-82267e30c92a",
"done": false
},
"mediaUserErrors": []
}
}
}

Anchor to Update collection imagesUpdate collection images

To update a collection's image, use collectionUpdate with the new image URL:

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL mutation

mutation UpdateCollectionImage {
collectionUpdate(input: {
id: "gid://shopify/Collection/1234567890"
image: {
src: "https://cdn.shopify.com/s/files/1/0574/7248/3417/files/myfilename.jpg"
altText: "Updated collection image"
}
}) {
collection {
id
image {
url
altText
}
}
userErrors {
field
message
}
}
}

JSON response

{
"data": {
"collectionUpdate": {
"collection": {
"id": "gid://shopify/Collection/1234567890",
"image": {
"url": "https://cdn.shopify.com/s/files/1/0574/7248/3417/files/myfilename.jpg",
"altText": "Updated collection image"
}
},
"userErrors": []
}
}
}

Anchor to Step 7: Delete filesStep 7: Delete files

Use fileDelete to permanently remove files from your shop. This is useful for removing discontinued product images, cleaning up test files, or freeing storage space.

Caution

This permanently deletes the file and all its associations with products, variants, and other resources. Any products referencing the deleted file will have broken media references.

POST https://{shop}.myshopify.com/api/{api_version}/graphql.json

GraphQL mutation

mutation DeleteFile {
# Pass one or more file IDs to delete.
fileDelete(fileIds: ["gid://shopify/MediaImage/1234567890"]) {
deletedFileIds
userErrors {
field
message
}
}
}

JSON response

{
"data": {
"fileDelete": {
"deletedFileIds": [
"gid://shopify/MediaImage/1234567890"
],
"userErrors": []
}
}
}


Was this page helpful?