GuidesVariant Media Gallery

Per-Variant Media Gallery

For merchants: when the supplier provides multiple photos for a color, PromoSync attaches them all to the matching variant (front, back, side, lifestyle), so shoppers see every angle of the color they picked without leaving the variant. Coverage depends on what the supplier publishes; some products and some colors only ship with a single image. Available on the Trial, Business, and Enterprise plans. The rest of this page is for Shopify theme developers who will render the gallery on the storefront.

Overview

The per-variant media gallery is a curated, ordered list of color-matched images attached to each Shopify product variant. It lives in a single metafield (psrestful.gallery) and contains every angle (front, back, side, lifestyle) for the variant’s specific color. The first item in the list is the variant’s featured image; the rest are additional views of the same color.

The feature exists because Shopify’s native data model only allows one image per variant. Merchants who sell apparel and other color-keyed products want shoppers to see every angle of the selected color, not just a single hero shot. By writing a list of file references at the variant level, themes can render a complete, color-aware gallery that swaps in lockstep with the variant picker.

The images come from the supplier’s PromoStandards media feed, surfaced through the PSRESTful API. At import time, PromoSync picks the color-matched images for each variant using supplier-specific association strategies (a color cascade for SanMar and S&S Activewear, partId-only matching for HIT, and a default cascade for other suppliers). The matched files are then uploaded to Shopify, and their GIDs are attached to the variant.

Plan eligibility

The psrestful.gallery variant metafield is populated only on the following PSRESTful plans:

PlanVariant gallery populated?Native variant.featured_image
TrialYesYes
BusinessYesYes
EnterpriseYesYes
FreeNoYes
StandardNoYes
PremiumNoYes
SupplierNoYes
SmallNoYes
MediumNoYes
LargeNoYes

On non-eligible plans, variants still receive a single featured image through Shopify’s native variant.featured_image, so storefronts see no regression; they just don’t get the multi-image gallery. Themes that follow the fallback pattern below work correctly on every plan.

To upgrade and unlock the gallery, see {{upgrade_url}}.

Data model

The gallery is stored as a single metafield on the variant:

PropertyValue
OwnerPRODUCTVARIANT
Namespacepsrestful
Keygallery
Typelist.file_reference
ValueOrdered list of Shopify File references (MediaImage, since the gallery only contains images)

Each item in the list is a Shopify File reference. PromoSync only writes image files, so in practice every entry dereferences to a MediaImage. The first item is always the variant’s featured image, and subsequent items are additional views for the same color, in the canonical PromoStandards order (primary → front → rear → left/right → inside/outside → other).

The featured image is duplicated into the gallery on purpose. It lets themes render a single ordered gallery without having to special-case the featured image (no need to prepend variant.featured_image separately, no risk of rendering it twice).

On products that have a per-variant gallery, product-level media in the Shopify Admin is also color-grouped, with every photo of one color sitting together (Black, Black, Black, Blue, Blue, Blue, …). Within each color, images are sorted primary → front → rear → left/right → inside/outside → other. PromoSync enforces this ordering with a productReorderMedia mutation that runs immediately after the initial productSet.

Liquid integration

The gallery metafield is list.file_reference, so each item in metafield.value is a File object, not a MediaImage directly. To render an image, read the file’s preview_image property.

Available per-file properties:

PropertyDescription
.preview_imageThe underlying image (use image_url)
.altAlt text
.media_typeimage, video, model, etc.

This is the recommended baseline. It reads psrestful.gallery, falls back to variant.featured_image on plans where the metafield is empty, and uses Shopify’s image filters with responsive widths and lazy loading.

{% raw %}{% assign variant = product.selected_or_first_available_variant %}
{% assign gallery = variant.metafields.psrestful.gallery.value %}
 
<div class="variant-gallery" data-variant-id="{{ variant.id }}">
  {% if gallery and gallery.size > 0 %}
    {% for file in gallery %}
      {% assign image = file.preview_image %}
      <figure class="variant-gallery__item" data-media-type="{{ file.media_type }}">
        {{ image | image_url: width: 1200 | image_tag:
             widths: '300, 600, 900, 1200',
             sizes: '(min-width: 750px) 50vw, 100vw',
             alt: file.alt,
             loading: 'lazy' }}
      </figure>
    {% endfor %}
  {% elsif variant.featured_image %}
    {# Fallback for shops without the gallery metafield (Free / Standard / Premium / etc.) #}
    <figure class="variant-gallery__item">
      {{ variant.featured_image | image_url: width: 1200 | image_tag:
           widths: '300, 600, 900, 1200',
           sizes: '(min-width: 750px) 50vw, 100vw',
           alt: variant.featured_image.alt,
           loading: 'lazy' }}
    </figure>
  {% endif %}
</div>{% endraw %}

Fallback to variant.featured_image

The fallback in the example above is the only thing required to keep free-tier and other non-eligible shops working. Because PromoSync still sets variant.image on every plan, the single-image experience is unchanged for those stores; your theme just renders one figure instead of many.

When a shopper picks a new variant, the gallery has to swap to the new variant’s color. There are two common patterns.

(a) Section Rendering API

This is the most robust option and requires no metafield-aware JavaScript. Shopify re-renders the section server-side with the new variant selected.

// Fired by Dawn-style themes when the picker changes the variant.
document.addEventListener('variant:change', async (event) => {
  const variantId = event.detail.variant.id;
  const sectionId = 'main-product'; // your section's id
 
  const url = `${window.location.pathname}?variant=${variantId}&section_id=${sectionId}`;
  const html = await fetch(url).then(r => r.text());
 
  const fresh = new DOMParser()
    .parseFromString(html, 'text/html')
    .querySelector('.variant-gallery');
 
  document.querySelector('.variant-gallery').replaceWith(fresh);
});

Because the request includes ?variant=ID, product.selected_or_first_available_variant resolves to the new variant on the server, and the Liquid above renders that variant’s gallery directly.

(b) Client-side JS using preloaded metafields

If you want to avoid the network round-trip, expose the metafield to JavaScript and swap the DOM in place. You’ll need to make the metafield available in the page, either by serializing it into a <script type="application/json"> tag or by enabling Storefront API access on the metafield definition.

{% raw %}<script type="application/json" id="variant-galleries">
{
  {% for variant in product.variants %}
    "{{ variant.id }}": [
      {% for file in variant.metafields.psrestful.gallery.value %}
        {
          "src": {{ file.preview_image | image_url: width: 1200 | json }},
          "alt": {{ file.alt | json }}
        }{% unless forloop.last %},{% endunless %}
      {% endfor %}
    ]{% unless forloop.last %},{% endunless %}
  {% endfor %}
}
</script>{% endraw %}
const galleries = JSON.parse(
  document.getElementById('variant-galleries').textContent
);
 
document.addEventListener('variant:change', (event) => {
  const items = galleries[event.detail.variant.id] || [];
  const container = document.querySelector('.variant-gallery');
  container.innerHTML = items.map(item => `
    <figure class="variant-gallery__item">
      <img src="${item.src}" alt="${item.alt || ''}" loading="lazy" />
    </figure>
  `).join('');
});

The Section Rendering approach is easier to maintain (one source of truth in Liquid). The client-side approach is faster on swap but duplicates rendering logic between Liquid and JS.

Defining the metafield in Theme Customizer

For the metafield to be usable as metafield.value (auto-dereferenced to file objects), it must be registered as a metafield definition in Shopify Admin under Settings → Custom data → Variants. Without a definition, .value returns the raw JSON of GIDs and .preview_image won’t work.

PromoSync registers this definition automatically on app install via bin/create_metafields.py. Merchants can verify it exists by visiting:

Settings → Custom data → Variants → “Variant Gallery”

The expected definition shape:

FieldValue
NameVariant Gallery
Namespacepsrestful
Keygallery
TypeList of file references
ValidationImage files only

If the definition is missing (most often because PromoSync was installed before this feature shipped and the merchant hasn’t re-triggered the metafield setup), the metafield values may still be written, but .value won’t dereference to file objects in Liquid. Have the merchant re-run the metafield setup from the PromoSync app or contact support at {{support_url}}.

Behavior details worth knowing

A few non-obvious rules are applied at import time. Knowing them will save you from chasing “missing image” reports that aren’t actually bugs.

  • Swatches are filtered out. PromoStandards class type 1004 (color swatches) never appear in the gallery. Use the variant’s color_array metafield if you need to render a swatch UI.
  • Low-resolution images are filtered out. Any image below the shop’s configured Minimum Image Resolution (set in PromoSync shop settings) is dropped at the source. This includes SanMar’s 300×450 “shared primary” filler image, which would otherwise pollute every variant gallery.
  • by_color shops skip the gallery. Shops configured with variant_grouping_strategy='by_color' create one Shopify product per color (e.g. Hit-a-Double-style stores). The metafield is not written on these shops because the product itself is already color-curated. Use Shopify’s standard product.media instead.
  • “Import first image only” disables extras. When the merchant enables Import first image only in import settings, the gallery collapses to just the featured image. The metafield is still written (so your theme code keeps working), but it will contain a single item.

Troubleshooting

  1. Verify the metafield definition exists at Settings → Custom data → Variants → “Variant Gallery”. Without a definition, .value returns raw JSON instead of file objects, and the {% raw %}{% if gallery and gallery.size > 0 %}{% endraw %} guard will treat it as falsy.
  2. Re-import the affected product. The gallery metafield is populated at import time only; it is not retroactively backfilled on variants that were imported before the feature was enabled or before the plan was upgraded.

Either:

  • The shop’s plan does not include the gallery feature (see Plan eligibility). The theme is correctly falling back to variant.featured_image. Upgrade at {{upgrade_url}}.
  • The supplier’s media feed had no color-matched extras for that variant (common for accessory items, single-angle products, or smaller suppliers).
  • The shop has Import first image only turned on in import settings.

Images out of order in Shopify Admin but correct on the storefront

The productReorderMedia mutation that color-groups product-level media runs asynchronously after import. The Admin Media tab can take up to a minute to settle. The variant gallery itself (psrestful.gallery) is written in order in the same mutation that creates the metafield, so the storefront order is correct immediately.

If the Admin order is still wrong after a few minutes, contact support at {{support_url}} with the product handle.

If variant.featured_image is nil and the gallery metafield is also empty, the supplier’s media feed simply didn’t publish any image for that color of that product. This is unrelated to the plan or the metafield definition — PromoSync only attaches images the supplier actually provides. It shows up most often on niche, discontinued, or brand-new SKUs.

Verify by opening the product on PSRESTful and checking the Media Content response for that part. If the supplier later adds images, re-importing the product will pick them up.