Merchants can [add media](https://help.shopify.com/en/manual/products/product-media/add-media) to their products, like images, 3D models, videos, and YouTube or Vimeo videos.

In this tutorial, you'll learn how to support product media in your theme.

## Resources

- The `media` attribute of the [`product` object](/docs/api/liquid/objects/product#product-media)
- [Media filters](/docs/api/liquid/filters/media-filters)

## Implementing product media

Product media is usually displayed on the [product page](/docs/storefronts/themes/architecture/templates/product). However, you might want to display product media in other areas of your theme, so it's recommended to build your media display in a [snippet](/docs/storefronts/themes/architecture#snippets) so that it can be reused.

To display product media, you can loop through the `media` attribute of the `product` object and apply the associated media filter, depending on the media type.

### Example

If you want to output product media on the product page, and your product page content is hosted in a `product.liquid` section, then you might do the following:

- Create a snippet called `media.liquid` to host your media display.
- Render `media.liquid` in your `product.liquid` section.

<p>
<div class="react-code-block" data-preset="file">
<div class="react-code-block-preload ThemeMode-dim">
<div class="react-code-block-preload-bar "></div>
<div class="react-code-block-preload-placeholder-container">
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>

</div>
</div>

<script data-option="filename" data-value="sections/product.liquid"></script>

<script type="text/plain" data-language="liquid">
RAW_MD_CONTENT
{% for media in product.media %}
  {% render 'media', media: media %}
{% endfor %}

END_RAW_MD_CONTENT</script>

</div>
</p>


<p>
<div class="react-code-block" data-preset="file">
<div class="react-code-block-preload ThemeMode-dim">
<div class="react-code-block-preload-bar "></div>
<div class="react-code-block-preload-placeholder-container">
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>
<div class="react-code-block-preload-code-container">
<div class="react-code-block-preload-codeline-number"></div>
<div class="react-code-block-preload-codeline"></div>
</div>

</div>
</div>

<script data-option="filename" data-value="snippets/media.liquid"></script>

<script type="text/plain" data-language="liquid">
RAW_MD_CONTENT
{% case media.media_type %}
  {% when 'image' %}
    <div class="product-single__media" style="padding-top: {{ 1 | divided_by: media.aspect_ratio | times: 100}}%;" data-media-id="{{ media.id }}">
      {{ media | image_url: width: 2048, height: 2048 | image_tag }}
    </div>
  {% when 'external_video' %}
    <div class="product-single__media" style="padding-top: {{ 1 | divided_by: media.aspect_ratio | times: 100}}%;" data-media-id="{{ media.id }}">
      {{ media | external_video_tag }}
    </div>
  {% when 'video' %}
    <div class="product-single__media" data-media-id="{{ media.id }}">
      {{ media | video_tag: controls: true }}
    </div>
  {% when 'model' %}
    <div class="product-single__media" style="padding-top: 100%" data-media-id="{{ media.id }}">
      {{ media | model_viewer_tag }}
    </div>
  {% else %}
    <div class="product-single__media" style="padding-top: 100%;" data-media-id="{{ media.id }}">
      {{ media | media_tag }}
    </div>
{% endcase %}

END_RAW_MD_CONTENT</script>

</div>
</p>


Each media type in the example above is wrapped in a `<div>` element with custom `style` and data attributes. These are based on the considerations documented in [UX considerations](#ux-considerations), and should be adjusted accordingly to match your approach.

> Tip:
> For another example of supporting media in a theme, you can refer to Dawn's implementation in the [`main-product.liquid` section](https://github.com/Shopify/dawn/blob/main/sections/main-product.liquid) and [`product-thumbnail.liquid` snippet](https://github.com/Shopify/dawn/blob/main/snippets/product-thumbnail.liquid).

## UX considerations

Every theme requires a different approach to create responsive media that works across all screen sizes and devices. The following general recommendations can help ensure that you're offering a good customer experience:

- [Make media elements responsive](#responsive-media-elements)
- [Preserve media element interactivity](#interactive-media-elements)

A product can have multiple videos, so if your theme has a thumbnail view for each media element, or displays multiple media elements at once, you should ensure that only the active video is playing.

> Tip:
> For more in-depth information, refer to [Product media UX guidelines](/docs/storefronts/themes/product-merchandising/media/media-ux).

### Responsive media elements

Shopify-hosted 3D models use [Google’s model viewer](https://modelviewer.dev/) component, and externally rendered videos are rendered in `<iframe>` elements. Neither of these are responsive containers by default.

Shopify-hosted videos are rendered in HTML5 video players, which are responsive by default, however only once they’re rendered.

Given the above, you should consider using an [aspect ratio box](https://css-tricks.com/aspect-ratio-boxes/) to create a responsive container for each.

> Tip:
> 3D models don’t have predefined aspect ratios, so it’s common practice to create a square container by setting `padding-top` to `100%`.

### Interactive media elements

Shopify-hosted, and externally-hosted, video elements, and 3D models have interactive components. For example, videos have progress bars and volume control, and 3D models can be rotated.

If any of these media elements are hosted in a carousel or swipe-interactive display, then the interactive components shouldn’t interfere with the ability to interact with the display.

## Use media preview images

Every [media type](/docs/api/liquid/objects/media#media-preview_image) has a `preview_image` attribute. This could be useful in scenarios like the following:

- [Product thumbnails](#product-thumbnails)
- [Social media images](#social-media-images)

> Tip:
> Applying the `image_url` Liquid [URL filter](/docs/api/liquid/filters/image_url) to the media object returns the `preview_image` URL.

### Product thumbnails

If your theme displays thumbnails for each media source on the product, then you can utilize the `preview_image` attribute of the media object in order to show a thumbnail image for each media source.

For example:

```liquid

{% if product.media.size > 1 %}
  <div class="thumbnails-wrapper">
    {% for media in product.media %}
      <a data-thumbnail-id="{{ media.id }}">
        {{ media.preview_image | image_url: width: 110, height: 110, scale: 2 | image_tag: media.alt, 'product-single__thumbnail-image' }}
      </a>
    {% endfor %}
  </div>
{% endif %}

```

> Tip:
> The above example adds a `data-thumbnail-id` attribute which is intended to be used in conjunction with the `data-media-id` attribute that’s included in the general media loop example above. This gives you an easy way to associate a thumbnail with its media display.

### Social media images

Rather than only showing images for social media previews, you can include media preview images as well.

For example:

```liquid

{%- if template.name == 'product' -%}
  {%- assign og_title = product.title | strip_html -%}
  {%- assign og_type = 'product' -%}

  {%- if product.media.size > 0 -%}
    {%- capture og_image_tags -%}
      {% for media in product.media limit:3 -%}
        <meta property="og:image" content="http:{{ media | image_url: width: 1200 height: 1200 }}">
      {%- endfor %}
    {%- endcapture -%}

    {%- capture og_image_secure_url_tags -%}
      {% for media in product.media limit:3 -%}
        <meta property="og:image:secure_url" content="https:{{ media | image_url: with: 1200, height: 1200 }}">
      {%- endfor %}
    {%- endcapture -%}
  {%- endif -%}
{%- endif -%}

```
## Support AR functionality

If merchants have 3D models of their products, then you can give them the option to showcase those models through AR. To do this, you can use the Shopify-XR library to support AR Quick Look in iOS’s Safari, and Android’s Scene Viewer.

You need to do the following to use this library:

  - [Initialize the library](#initialize-the-library)
  - [Launch the display](#launch-the-display)

### Initialize the library

The following JavaScript needs to be included on product pages to initialize the library:

```js
<script>
  function setupShopifyXr(){
    if (!window.ShopifyXR) {
      document.addEventListener('shopify_xr_initialized', function() {
        setupShopifyXr();
      });
    }else{
      window.ShopifyXR.addModels();
      window.ShopifyXR.setupXRElements();
    }
  }

  window.Shopify.loadFeatures([
    {
      name: 'shopify-xr',
      version: '1.0',
      onLoad: setupShopifyXr
    }
  ]);
</script>
```

### Launch the display

You can launch the display in two ways:

  - [With a button](#launch-the-display-with-a-button)
  - [With JavaScript](#launch-the-display-with-javascript)

#### Launch the display with a button

You can launch the display with a button that has the following attributes:

<table>
  <thead>
    <tr>
      <th style="width: 40%">Attribute</th>
      <th style="width: 60%">Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>data-shopify-xr</code></td>
      <td>The Shopify-XR library scans the DOM for elements with this attribute and attaches a click handler to launch the display.</td>
    </tr>
    <tr>
      <td><code>data-shopify-model3d-id</code></td>
      <td>The <a href="/docs/api/liquid/objects/media#media-id">media ID</a> of the current model.</td>
    </tr>
    <tr>
      <td><code>data-shopify-title</code></td>
      <td>The title of the product.</td>
    </tr>
    <tr>
      <td><code>data-shopify-xr-hidden</code></td>
      <td>A base data attribute for the Shopify-XR library to reference.</td>
    </tr>
  </tbody>
</table>

You would include a button for each model type media source.

For example:

```liquid

{% for media in product.media %}
  {% case media.type %}
    ...
    {% when 'model' %}
      <div class="product-single__media" style="padding-top: 100%" data-media-id="{{ media.id }}">
        {{ media | model_viewer_tag }}
      </div>

      <button
        data-shopify-xr
        data-shopify-model3d-id="{{ media.id }}"
        data-shopify-title="{{ product.title | escape }}"
        data-shopify-xr-hidden
      />
    ...
  {% endcase %}
{% endfor %}

```
#### Launch the display with JavaScript

Rather than include a button to launch the display, you can use JavaScript. For example:

```liquid

<script>
  window.ShopifyXR.launchXR({
    model3dId: [media-id],
    title: "{{ product.title | escape }}",
  });
</script>

```

> Note:
> In the example above, `[media-id]` represents the [media ID](/docs/api/liquid/objects/media#media-id) for the associated model.

## Control video functionality with parameters

Shopify hosted videos can have all [HTML5 video attributes](https://developer.mozilla.org/docs/Web/HTML/Element/video#attributes) set when they’re rendered with the Liquid [video_tag](/docs/api/liquid/filters/video_tag) or [media_tag](/docs/api/liquid/filters/media_tag) filter. For example:

  - `autoplay` - Whether to automatically play the video after it’s loaded.
  - `loop` - Whether to loop the video.
  - `muted` - Whether to mute the video’s audio.
  - `controls` - Whether a user can control the video playback.

Each parameter is `false` by default, however you can set them to be `true` like the following:

```liquid

<!-- Autoplay a video -->
{{ media | video_tag: autoplay: true }}

<!-- Autoplay a video, and loop it -->
{{ media | video_tag: autoplay: true, loop: true }}

<!-- Autoplay a video, loop it, and mute it -->
{{ media | video_tag: autoplay: true, loop: true, muted: true }}

<!-- Autoplay a video, loop it, mute it, and allow the user to control the video playback -->
{{ media | video_tag: autoplay: true, loop: true, muted: true, controls: true }}

```

> Tip:
> You can control these same behaviors for externally-hosted videos using the Liquid [external_video_url](/docs/api/liquid/filters#external_video_url) filter. However, the available parameters depend on the video host.