---
title: Manage media for products and collections
description: Learn how to manage product and collection media using the GraphQL Admin API.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/manage-media
  md: >-
    https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/manage-media.md
api_name: admin
---

# 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.

***

## Requirements

* Your app can make [authenticated requests](https://shopify.dev/docs/api/admin-graphql#authentication) to the latest version of the GraphQL Admin API or higher.
* Your app has the `read_products`, `write_products`, and `write_files` [access scopes](https://shopify.dev/docs/api/usage/access-scopes#authenticated-access-scopes). Learn how to [configure your access scopes using Shopify CLI](https://shopify.dev/docs/apps/build/cli-for-apps/app-configuration).
* You've created products to associate media with. If you need to create products first, refer to [Add product data](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/add-data).

***

## How it works

The GraphQL Admin API manages files independently from products using a unified file system. Upload a file once to Shopify's CDN using [`fileCreate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/fileCreate) or [`stagedUploadsCreate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/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.

### Supported media types

| Media type | Formats | Size limits | Key details |
| - | - | - | - |
| [**MediaImage**](https://shopify.dev/docs/api/admin-graphql/latest/objects/mediaimage) | PNG, GIF, JPEG, WEBP, HEIC | Max 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 |
| [**Video**](https://shopify.dev/docs/api/admin-graphql/latest/objects/video) | MP4, MOV, WEBM | Max 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. |
| [**ExternalVideo**](https://shopify.dev/docs/api/admin-graphql/latest/objects/externalvideo) | YouTube, Vimeo | No limits (hosted externally) | No file upload required. Provide the embed or share URL. Doesn't count against shop's storage quota. |
| [**Model3d**](https://shopify.dev/docs/api/admin-graphql/latest/objects/model3d) | GLB, USDZ | Max 500 MB | For augmented reality on supported devices. Shopify serves models in both GLB (web) and USDZ (iOS AR) formats. |

***

## Step 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:

| Approach | Best for | How it works |
| - | - | - |
| [`fileCreate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/fileCreate) | Simple uploads from accessible URLs | Provide a URL and Shopify downloads, processes, and stores the file. |
| [`stagedUploadsCreate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/stagedUploadsCreate) | Large files, files on your server, or when you need more control | Get secure upload credentials, upload directly to Shopify's storage, then register the file. |

### Option A: Upload from a URL with `fileCreate`

Use [`fileCreate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/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

```graphql
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

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

### Option B: Upload directly with `stagedUploadsCreate`

Use [`stagedUploadsCreate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/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.

#### 1.​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

```graphql
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

```json
{
  "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": []
    }
  }
}
```

#### 2.​Upload the file

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:

```terminal
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:

```terminal
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](https://www.getpostman.com/) or [Insomnia](https://insomnia.rest/).

#### 3.​Register the uploaded file

After uploading, register the file with Shopify using [`fileCreate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/fileCreate). Use the `resourceUrl` from the staged upload response as the `originalSource`:

## POST https://{shop}.myshopify.com/api/{api\_version}/graphql.json

## GraphQL mutation

```graphql
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

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

***

## Step 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

```graphql
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)

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

***

## Step 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`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productSet), [`productCreate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productCreate), or [`productUpdate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/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

```graphql
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

```json
{
  "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": []
    }
  }
}
```

***

## Step 4: Add media to product variants

Media belongs to the product, so a variant can reference only media that already exists on the product. After you've [added media to the product](#step-3-add-media-to-products), associate it with specific variants using [`productVariantsBulkCreate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productVariantsBulkCreate) (when you create variants) or [`productVariantsBulkUpdate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productVariantsBulkUpdate) (for existing variants). Both mutations accept the [`mediaId`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productVariantsBulkCreate#arguments-variants.fields.mediaId) and [`mediaSrc`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productVariantsBulkCreate#arguments-variants.fields.mediaSrc) fields on each variant.

**Note:**

The [`productCreate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productCreate) mutation doesn't accept a `variants` field, so you can't create variants or add variant media when you create a product. Use `productCreate` to define the product's options, and then create variants with [`productVariantsBulkCreate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productVariantsBulkCreate). To learn more, refer to [Add product data](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/add-data).

The following example associates media that's already on the product with two existing variants using [`productVariantsBulkUpdate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productVariantsBulkUpdate). Reference each media item by its `mediaId`:

## POST https://{shop}.myshopify.com/api/{api\_version}/graphql.json

## GraphQL mutation

```graphql
mutation AddMediaToVariants {
  productVariantsBulkUpdate(
    productId: "gid://shopify/Product/7882412064857",
    variants: [
      {
        id: "gid://shopify/ProductVariant/43729076682841",
        # Reference a media item that's already on the product.
        mediaId: "gid://shopify/MediaImage/25823372804185"
      },
      {
        id: "gid://shopify/ProductVariant/43729076715609",
        mediaId: "gid://shopify/MediaImage/25823372837012"
      }
    ]
  ) {
    productVariants {
      id
      media(first: 10) {
        nodes {
          alt
          mediaContentType
          ... on MediaImage {
            image {
              url
            }
          }
        }
      }
    }
    userErrors {
      field
      message
    }
  }
}
```

## JSON response

```json
{
  "data": {
    "productVariantsBulkUpdate": {
      "productVariants": [
        {
          "id": "gid://shopify/ProductVariant/43729076682841",
          "media": {
            "nodes": [
              {
                "alt": "Red t-shirt",
                "mediaContentType": "IMAGE",
                "image": {
                  "url": "https://cdn.shopify.com/s/files/1/0574/7248/3417/files/red_t_shirt.jpg"
                }
              }
            ]
          }
        },
        {
          "id": "gid://shopify/ProductVariant/43729076715609",
          "media": {
            "nodes": [
              {
                "alt": "Yellow t-shirt",
                "mediaContentType": "IMAGE",
                "image": {
                  "url": "https://cdn.shopify.com/s/files/1/0574/7248/3417/files/yellow_t_shirt.jpg"
                }
              }
            ]
          }
        }
      ],
      "userErrors": []
    }
  }
}
```

***

## Step 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`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/collectionCreate) or [`collectionUpdate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/collectionUpdate) with image URLs. You can use Shopify CDN URLs from uploaded files or external URLs from your own hosting.

### Option A: Use Shopify CDN files

To add a file from Shopify's CDN to a collection, first upload it with [`fileCreate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/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

```graphql
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

```json
{
  "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

```graphql
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

```json
{
  "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": []
    }
  }
}
```

### Option 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

```graphql
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

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

***

## Step 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.

### Update 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`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/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

```graphql
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

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

### Reorder 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`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productreordermedia) to change media order without deleting and re-adding items.

## POST https://{shop}.myshopify.com/api/{api\_version}/graphql.json

## GraphQL mutation

```graphql
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

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

### Update collection images

To update a collection's image, use [`collectionUpdate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/collectionUpdate) with the new image URL:

## POST https://{shop}.myshopify.com/api/{api\_version}/graphql.json

## GraphQL mutation

```graphql
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

```json
{
  "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": []
    }
  }
}
```

***

## Step 7: Delete files

Use [`fileDelete`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/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

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

## JSON response

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

***

## Next steps

* Learn how to [add product data](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/add-data).
* Learn how to [update product data](https://shopify.dev/docs/apps/build/product-merchandising/products-and-collections/update-data).
* Explore the [`fileCreate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/fileCreate) mutation in the GraphQL Admin API reference.

***
