Structured data for product groups
Add schema.org/ProductGroup JSON-LD to your product pages so Google treats your linked products as variants of each other. The markup unlocks rich results, correct Google Shopping behaviour, and proper variant clustering in search.
The data the snippet needs is already in your product metafields, so no external service is involved. The Liquid below reads it and emits valid ProductGroup markup.
Enable public metafields
Section titled “Enable public metafields”The snippet reads product.metafields.pl_swatches.groups, which only exists when public metafields are on. See Public metafields for how to enable the toggle and what gets written.
Save the snippet
Section titled “Save the snippet”Save this as snippets/platmart-structured-data.liquid in your theme:
{%- comment -%} Platmart Color Swatches - ProductGroup JSON-LD structured data Builds schema.org/ProductGroup markup from swatch group metafields.
Usage: {% render 'platmart-structured-data', product: product %}{%- endcomment -%}
{%- assign groups = product.metafields.pl_swatches.groups.value -%}{%- if groups == blank -%}{%- break -%}{%- endif -%}
{%- for group in groups -%} {%- comment -%} Skip groups that only show on collection pages {%- endcomment -%} {%- if group.display_for == "collections" -%}{%- continue -%}{%- endif -%}
{%- comment -%} We need at least 2 swatches for a ProductGroup to make sense {%- endcomment -%} {%- if group.swatches.size < 2 -%}{%- continue -%}{%- endif -%}
{%- comment -%} Find the current product in the swatches list {%- endcomment -%} {%- liquid assign current_swatch = nil for swatch in group.swatches if swatch.handle == product.handle assign current_swatch = swatch break endif endfor -%} {%- if current_swatch == nil -%}{%- continue -%}{%- endif -%}
{%- comment -%} Try to map the option name to a schema.org property. color, size, material, pattern are natively supported. Anything else falls back to additionalProperty. {%- endcomment -%} {%- liquid assign option_lower = group.option_name | downcase assign schema_properties = "color,size,material,pattern" | split: "," assign variant_property = blank for prop in schema_properties if option_lower == prop assign variant_property = prop break endif endfor -%}
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "ProductGroup", "name": {{ product.title | json }}, "productGroupID": {{ group.group_id | json }}, {%- if variant_property != blank -%} "variesBy": [{{ variant_property | prepend: "https://schema.org/" | json }}], {%- endif -%} "hasVariant": [ { "@type": "Product", "name": {{ product.title | json }}, "url": {{ product.url | prepend: request.origin | json }}, {%- if variant_property != blank -%} {{ variant_property | json }}: {{ current_swatch.name | json }}, {%- else -%} "additionalProperty": { "@type": "PropertyValue", "propertyID": {{ group.option_name | json }}, "value": {{ current_swatch.name | json }} }, {%- endif -%} {%- if product.featured_image -%} "image": {{ product.featured_image | image_url: width: 1200 | json }}, {%- endif -%} "offers": [ {%- for variant in product.variants -%} {%- if forloop.first == false -%},{%- endif -%} { "@type": "Offer", "name": {{ variant.title | json }}, "sku": {{ variant.sku | json }}, "price": {{ variant.price | divided_by: 100.0 }}, "priceCurrency": {{ cart.currency.iso_code | json }}, "availability": {%- if variant.available -%}"https://schema.org/InStock"{%- else -%}"https://schema.org/OutOfStock"{%- endif -%}, "url": {{ product.url | append: '?variant=' | append: variant.id | prepend: request.origin | json }} } {%- endfor -%} ] } {%- for swatch in group.swatches -%} {%- if swatch.handle == product.handle -%}{%- continue -%}{%- endif -%} {%- assign variant_url = product.url | replace: product.handle, swatch.handle | prepend: request.origin -%} ,{ "@type": "Product", "url": {{ variant_url | json }} } {%- endfor -%} ] } </script>{%- endfor -%}The snippet handles the edge cases: it skips groups set to display on collections only, requires at least two swatches per group to emit anything, and maps common option names (color, size, material, pattern) to native schema.org properties, falling back to additionalProperty for anything else.
Render it on product pages
Section titled “Render it on product pages”In your product template (usually sections/main-product.liquid or templates/product.liquid), add:
{% render 'platmart-structured-data', product: product %}The script tag renders inline. Google picks it up the next time it crawls the page.
Remove duplicate ProductGroup markup
Section titled “Remove duplicate ProductGroup markup”You only want one ProductGroup block per page. Some themes already emit one (recent OS 2.0 themes often do). Search your theme for schema.org/ProductGroup or {{ product | structured_data }} and either delete the duplicate or comment it out:
{% comment %} Replaced by Platmart structured data {{ product | structured_data }}{% endcomment %}Multiple ProductGroup blocks on the same page won’t break rendering, but Google can’t tell which one is canonical, which hurts indexing.
Test the output
Section titled “Test the output”Run a product page through one of these:
- Google’s Rich Results Test, which shows whether Google can read the markup and previews the rich result.
- Schema.org Validator, which checks the markup against the spec.
Both accept a URL or pasted HTML. The Rich Results Test is the more useful one day-to-day, since it tells you what Google will actually do with what you emit.
Example output
Section titled “Example output”For a “Backpack - Red” product in a Color group with Red, Blue, and White, with sizes S/M/L:
{ "@context": "https://schema.org", "@type": "ProductGroup", "name": "Backpack - Red", "productGroupID": 59, "variesBy": ["https://schema.org/color"], "hasVariant": [ { "@type": "Product", "name": "Backpack - Red", "url": "https://example.myshopify.com/products/backpack-red", "color": "Red", "image": "https://cdn.shopify.com/.../backpack-red.jpg", "offers": [ { "@type": "Offer", "name": "S", "sku": "BP-RED-S", "price": 49.99, "priceCurrency": "USD", "availability": "https://schema.org/InStock", "url": "https://example.myshopify.com/products/backpack-red?variant=1001" } ] }, { "@type": "Product", "url": "https://example.myshopify.com/products/backpack-blue" }, { "@type": "Product", "url": "https://example.myshopify.com/products/backpack-white" } ]}The current product gets the full breakdown including offers and image. Linked variants appear as Product entries with just a URL, which gives Google enough to crawl them as siblings.