Shopify Product Variant Schema: How to Mark Up Sizes and Colors with ProductGroup

Shopify Product Variant Schema Guide

If your Shopify products come in sizes or colors, Google wants to see them as one ProductGroup with a separate Product for each variant, not as one product with a single price. Each variant needs its own ID (SKU or GTIN), its own price and stock status, and its own URL that opens the page with that variant already selected. Shopify gives you all of this out of the box. Most variant schema problems come from markup that ignores it.

This guide is for Shopify store owners and developers who sell products with options like Size, Color or Material. You’ll see what Google requires, how each Shopify field maps to the schema, a working example, the mistakes we see most often, and how to test the result.

What is product variant schema?

Product variant schema is structured data that tells search engines that several items are versions of the same product. A t-shirt in five sizes and three colors is one product group with fifteen variants.

It uses four main pieces:

Property What it means Shopify example
ProductGroup The parent product that all variants belong to “Linen Kurta”
productGroupID A unique ID for the whole group The Shopify product ID
variesBy The options that change between variants https://schema.org/size, https://schema.org/color
hasVariant The list of variants, each marked up as a Product “Linen Kurta – M / Blue”

Without variant markup, Google sees a single product with a single price. That’s a problem when sizes have different prices, or when some sizes are out of stock and others aren’t.

What Google requires for product variants

Google’s >product variant structured data documentation (last updated September 2026) sets out these rules:

  • Every variant needs a unique ID. Use sku or gtin, and make sure no two variants share one.
  • The group needs a unique ID. Set productGroupID on the ProductGroup, or inProductGroupWithID on each variant. Using both is fine as long as they match.
  • Each variant must be reachable on its own URL. Google says the site “must have the ability to preselect each variant directly with a distinct URL (using URL query parameters)”.
  • Each variant needs its own offer. That means its own price, priceCurrency, availability and url.
  • Variants follow the normal Product rules too. Google asks you to include the usual merchant listing properties (name, image, offer) on each variant.

Google lists color, size, material, pattern, suggestedAge and suggestedGender as the properties you can use in variesBy.

Shopify is a “single-page” site

Google describes two setups. A single-page site shows all variants on one product page. A multi-page site gives each variant a completely separate page.

Shopify is single-page. Every variant lives on the same product URL, and Shopify lets you open a specific variant by adding ?variant= and the variant ID, as >Shopify’s own partner guide explains:

https://yourstore.com/products/linen-kurta?variant=44012345678901

That means:

  • The ProductGroup gets one URL, the normal product URL.
  • Each variant’s offer gets the product URL plus its ?variant= ID.

How Shopify variant data maps to schema

You don’t need to invent anything. Shopify already stores every value the schema needs:

Schema property Shopify field Notes
ProductGroup name Product title
productGroupID Product ID Same for every variant
variesBy Option names (Size, Color…) Only standard options map cleanly
ProductGroup url Product URL No ?variant= here
Variant name Product title + variant title “Linen Kurta – M / Blue”
Variant sku Variant SKU Must be unique per variant
Variant gtin Variant barcode Only if it’s a real GTIN/EAN/UPC
Variant size / color Variant option values
Variant image Variant image, or the product image
Offer url Product URL + ?variant=<id> Must preselect the variant
Offer price Variant price Shopify stores prices in cents internally
Offer availability Variant “available for sale” InStock or OutOfStock

Example: a product with size variants

Here is a shortened example for a dress sold in three sizes. Two sizes are in stock and one is sold out. The values are illustrative. Your IDs, URLs and prices will be different.

{
  "@context": "https://schema.org",
  "@type": "ProductGroup",
  "name": "Linen Kurta",
  "description": "Hand-block printed linen kurta with side pockets.",
  "url": "https://yourstore.com/products/linen-kurta",
  "brand": { "@type": "Brand", "name": "Your Brand" },
  "productGroupID": "8897463681076",
  "variesBy": ["https://schema.org/size"],
  "hasVariant": [
    {
      "@type": "Product",
      "name": "Linen Kurta - S",
      "sku": "LK-001-S",
      "size": "S",
      "image": "https://cdn.shopify.com/s/files/.../linen-kurta.jpg",
      "inProductGroupWithID": "8897463681076",
      "offers": {
        "@type": "Offer",
        "url": "https://yourstore.com/products/linen-kurta?variant=44012345678901",
        "price": "2499.00",
        "priceCurrency": "INR",
        "availability": "https://schema.org/InStock",
        "itemCondition": "https://schema.org/NewCondition"
      }
    },
    {
      "@type": "Product",
      "name": "Linen Kurta - M",
      "sku": "LK-001-M",
      "size": "M",
      "image": "https://cdn.shopify.com/s/files/.../linen-kurta.jpg",
      "inProductGroupWithID": "8897463681076",
      "offers": {
        "@type": "Offer",
        "url": "https://yourstore.com/products/linen-kurta?variant=44012345678902",
        "price": "2499.00",
        "priceCurrency": "INR",
        "availability": "https://schema.org/InStock",
        "itemCondition": "https://schema.org/NewCondition"
      }
    },
    {
      "@type": "Product",
      "name": "Linen Kurta - L",
      "sku": "LK-001-L",
      "size": "L",
      "image": "https://cdn.shopify.com/s/files/.../linen-kurta.jpg",
      "inProductGroupWithID": "8897463681076",
      "offers": {
        "@type": "Offer",
        "url": "https://yourstore.com/products/linen-kurta?variant=44012345678903",
        "price": "2599.00",
        "priceCurrency": "INR",
        "availability": "https://schema.org/OutOfStock",
        "itemCondition": "https://schema.org/NewCondition"
      }
    }
  ]
}

Notice three things. Every variant has a different SKU. Every offer URL ends in a different ?variant= ID. And the L size has its own price and its own “out of stock” status.

Common variant schema mistakes on Shopify

Recently we helped a fashion store whose products came in seven sizes, from XS to XXL plus a Custom option. Their hand-written schema looked fine at first glance, but it had the first two problems below. Both are easy to miss because the JSON is still valid.

1. The same SKU on every variant

Many stores give every size the same SKU, because the SKU describes the design, not the size. Google treats the SKU as the variant’s unique ID, so seven variants with one SKU look like one item listed seven times.

Fix: give each variant its own SKU in Shopify, for example by adding the size: DRESS-104-S, DRESS-104-M. If your variants have real barcodes, add them too, because they become the gtin.

2. Every variant pointing to the same URL

If every variant’s offer url is the plain product URL, Google can’t open a specific size or color. That breaks Google’s rule that each variant must be reachable on a distinct URL.

Fix: use the ?variant= URL for each variant’s offer. Open one in a private browser window and check that the right size or color is already selected.

3. Two product schemas on the same page

Many Shopify themes already output their own Product markup. If you add ProductGroup markup from an app or a snippet without turning the theme’s markup off, Google gets two different descriptions of the same product, and they can disagree on price or stock.

Fix: view the page source or run it through the Rich Results Test. If you see two product schemas, remove one of them.

4. Prices or stock that don’t match the page

Schema has to match what a shopper sees. If a variant is on sale on the page but the schema shows the old price, or a size is sold out but the schema says InStock, that’s a mismatch. In Liquid, remember that variant.price is stored in cents, so 2499 rupees comes out as 249900 unless you divide it.

5. Options that aren’t valid variesBy values

variesBy only accepts the properties Google lists, such as size, color, material and pattern. A custom option like “Style” or “Pack” isn’t one of them. You can still sell those variants and mark each one up. Just don’t put an invented value in variesBy.

How to add variant schema to your Shopify store

Option 1: SEO Schema Pro (no code)

>SEO Schema Pro switches a product to ProductGroup markup automatically as soon as it has more than one variant. It maps Size, Color, Material and Pattern options to variesBy. It also fills in each variant’s name, image, SKU, barcode (as GTIN), size, color, price and stock straight from Shopify.

If you want one format across your whole catalog, you can give Schema Buddy, the app’s AI assistant, a reference schema. It previews the result on a few products first, then applies the same structure to every product using each product’s own data. You can undo the change afterwards.

Option 2: Liquid snippet (for developers)

If you prefer to edit your theme, here is a starting point. It outputs a ProductGroup only when a product has more than one variant. Because themes differ, test it on a duplicate theme first, and remove any product schema your theme already outputs.

{%- if product.variants.size > 1 -%}
{%- assign varies_by = '' -%}
{%- for option in product.options -%}
  {%- assign option_name = option | downcase -%}
  {%- if option_name == 'size' -%}
    {%- assign varies_by = varies_by | append: '"https://schema.org/size",' -%}
  {%- elsif option_name == 'color' or option_name == 'colour' -%}
    {%- assign varies_by = varies_by | append: '"https://schema.org/color",' -%}
  {%- endif -%}
{%- endfor -%}
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "ProductGroup",
  "name": {{ product.title | json }},
  "description": {{ product.description | strip_html | strip | json }},
  "url": {{ request.origin | append: product.url | json }},
  "brand": { "@type": "Brand", "name": {{ product.vendor | json }} },
  "productGroupID": "{{ product.id }}",
  "variesBy": [{{ varies_by | remove_last: ',' }}],
  "hasVariant": [
    {%- for variant in product.variants -%}
    {%- assign variant_image = variant.featured_image | default: product.featured_image -%}
    {
      "@type": "Product",
      "name": {{ product.title | append: ' - ' | append: variant.title | json }},
      "sku": {{ variant.sku | json }},
      {%- if variant.barcode != blank %}
      "gtin": {{ variant.barcode | json }},
      {%- endif %}
      {%- if variant_image %}
      "image": {{ variant_image | image_url: width: 1200 | prepend: 'https:' | json }},
      {%- endif %}
      "inProductGroupWithID": "{{ product.id }}",
      {%- for option in product.options -%}
        {%- assign option_name = option | downcase -%}
        {%- if option_name == 'size' %}
      "size": {{ variant.options[forloop.index0] | json }},
        {%- elsif option_name == 'color' or option_name == 'colour' %}
      "color": {{ variant.options[forloop.index0] | json }},
        {%- endif -%}
      {%- endfor %}
      "offers": {
        "@type": "Offer",
        "url": {{ request.origin | append: variant.url | json }},
        "price": {{ variant.price | divided_by: 100.0 | json }},
        "priceCurrency": {{ cart.currency.iso_code | json }},
        "availability": "https://schema.org/{% if variant.available %}InStock{% else %}OutOfStock{% endif %}",
        "itemCondition": "https://schema.org/NewCondition"
      }
    }{%- unless forloop.last -%},{%- endunless -%}
    {%- endfor -%}
  ]
}
</script>
{%- endif -%}

A few notes on the snippet:

  • variant.url already includes ?variant= and the variant ID, which gives each offer its own URL.
  • Products with a single variant are skipped. Use normal Product markup for those.
  • If a variant has no SKU and no barcode, it has no unique ID. Fill in the SKU in Shopify.

For a broader walkthrough of editing theme code, see our guide on how to add custom SEO schema in Shopify.

How to test your variant schema

  1. Open the >Rich Results Test and test a live product URL. Look for Merchant listings and Product snippets in the results. Variants should appear inside the product group.
  2. Check the warnings, not just the errors. A missing optional field is fine. A duplicate SKU or a missing offer is not.
  3. Open two or three variant URLs from the schema in a private window. Each should load with that size or color selected.
  4. Compare price and stock in the schema with what the page shows for the same variant.
  5. Watch Google Search Console. The Merchant listings and Product snippets reports show errors across your whole store. Fixes can take days or weeks to show, because Google has to recrawl the pages.

Valid markup makes your products eligible for these features. It doesn’t guarantee that Google will show them.

FAQ

Do I need ProductGroup if my product has only one variant?

No. A product with a single variant should use normal Product markup. ProductGroup is for products that come in several versions.

Does variant schema improve my Google rankings?

Schema isn’t a direct ranking factor. It helps Google understand your products correctly, including which sizes or colors exist and what each costs, and makes them eligible for shopping features. That’s the benefit, not a ranking boost.

Should each size have its own product page?

Not on Shopify. Keeping variants on one product page and linking to them with ?variant= is the setup Google calls a single-page site, and it’s fully supported.

Can I mark up more than one option, like size and color together?

Yes. List both in variesBy and give each variant both a size and a color value.

Conclusion

Variant schema comes down to three rules: one ProductGroup per product, a unique ID for every variant, and a distinct ?variant= URL for every offer. Shopify already has the data. Your markup just has to use it. Start with your best-selling products, test them in the Rich Results Test, then roll the same structure out across your catalog.

New to schema? Start with our Shopify schema markup guide. If you use review apps, also read Google’s 2026 review schema rules for Shopify.

Give Google what it's looking for.

Install SEO Schema Pro and have your first product schema live in under five minutes.
WhatsApp icon