Add structured data (JSON-LD) for product groups

Platmart Swatches stores swatch group data in public metafields on each product. You can use this data to add ProductGroup structured data (JSON-LD) to your product pages - helping search engines understand that your separate Shopify products are actually variants of the same product.

This guide walks you through building a Liquid snippet that outputs ProductGroup JSON-LD using the swatch metafields.

The Liquid snippet

Create a new file in your theme at snippets/platmart-structured-data.liquid and paste this in:

{%- comment -%}
  Platmart Swatches - ProductGroup JSON-LD structured data
  Builds schema.org/ProductGroup markup from swatch group metafields.

  Usage: {% render 'platmart-structured-data', product: product %}

  Metafield docs: https://help.platmart.io/article/208-access-app-metafields
{%- 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 -%}

How to set it up

1. Enable public metafields

In the Platmart Swatches app, go to Settings and turn on public metafields. This makes the swatch data accessible to your theme code. (Here's how)

2. Add the snippet to your theme

Copy the code above into snippets/platmart-structured-data.liquid .

3. Render it on product pages

Drop this line into your product template (usually sections/main-product.liquid  or templates/product.liquid ):

{% render 'platmart-structured-data', product: product %}

Example from Horizon theme:


4. Get rid of duplicate structured data

You only want one ProductGroup  per page. Some themes output their own ProductGroup  JSON-LD - look in theme.liquid , main-product.liquid , or any structured-data  snippet and remove or disable it if so. For example, in Horizon themes this snippet is located in main-product.liquid  and needs to be commented out:

{% comment %}
<script type="application/ld+json">
  {{ product | structured_data }}
</script>
{% endcomment %}

5. Test it

Run your product page through Google's Rich Results Test or the Schema.org Validator to make sure everything looks right.


Example output

Say you're on the page for "Backpack - Red" which comes in sizes S, M, L, and belongs to a "Color" group with Red, Blue, and White. The snippet outputs something like this:


{
  "@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": "Offer",
          "name": "M",
          "sku": "BP-RED-M",
          "price": 49.99,
          "priceCurrency": "USD",
          "availability": "https://schema.org/InStock",
          "url": "https://example.myshopify.com/products/backpack-red?variant=1002"
        },
        {
          "@type": "Offer",
          "name": "L",
          "sku": "BP-RED-L",
          "price": 54.99,
          "priceCurrency": "USD",
          "availability": "https://schema.org/OutOfStock",
          "url": "https://example.myshopify.com/products/backpack-red?variant=1003"
        }
      ]
    },
    {
      "@type": "Product",
      "url": "https://example.myshopify.com/products/backpack-blue"
    },
    {
      "@type": "Product",
      "url": "https://example.myshopify.com/products/backpack-white"
    }
  ]
}

The current product gets full details with an offer per Shopify variant (each with its own SKU, price, and availability). The other color variants are just URLs - Google crawls those pages and picks up their details there.


Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.

Still need help? Contact Us Contact Us