How to Add Shopify Variant Metafields to JSON-LD for Rich Results Without Breaking Structured Data?

I’m trying to add variant-specific metafields to my Shopify product JSON-LD structured data to ensure that each variant gets properly indexed and displayed in Google’s rich results.

I have the following metafields at the variant level:

  • custom.strain
  • custom.strain_type
  • custom.category
  • custom.strain_genetics
  • custom.strain_description
  • custom.thc_percentage
  • custom.strain_effects
  • custom.3rd_party_test_results

I’m currently including these metafields in the offers section of the JSON-LD for each variant like code attached

"offers": [
  {%- for variant in product.variants -%}
    {
      "@type": "Offer",
      "price": "{{ variant.price | money_without_currency | replace: ',', '.' }}",
      "priceCurrency": "{{ shop.currency }}",
      "itemCondition": "https://schema.org/NewCondition",
      "url": "{{ shop.secure_url }}{{ variant.url }}",
      "sku": "{{ variant.sku }}",
      "mpn": "{{ variant.barcode }}",
      "availability": "https://schema.org/{%- if variant.available -%}InStock{%- else -%}OutOfStock{%- endif -%}",
      "priceValidUntil": {{ "today" | date: '%s' | plus: 2592000 | date: "%Y-%m-%d" | json }},
      "customAttributes": {
        "strain": "{{ variant.metafields.custom.strain }}",
        "strainType": "{{ variant.metafields.custom.strain_type }}",
        "category": "{{ variant.metafields.custom.category }}",
        "strainGenetics": "{{ variant.metafields.custom.strain_genetics }}",
        "strainDescription": "{{ variant.metafields.custom.strain_description | strip_html }}",
        "thcPercentage": "{{ variant.metafields.custom.thc_percentage }}",
        "strainEffects": "{{ variant.metafields.custom.strain_effects }}",
        "thirdPartyTestResults": "{{ variant.metafields.custom.3rd_party_test_results }}"
      }
    }{%- if forloop.index < product.variants.size -%},{%- endif -%}
  {%- endfor -%}
]

:

The issue I’m encountering is that Google’s Rich Results Test either fails to recognize multiple variants or shows only one variant without detecting any of the added metafields (customAttributes).

What am I doing wrong in incorporating these variant metafields in the JSON-LD? Is there a specific format or practice that ensures variant-specific data is properly picked up by Google’s schema validation tools? Any insight or best practices for handling variant metafields in Shopify JSON-LD would be greatly appreciated.

Thanks in advance for any help!

Give it a try: https://feedarmy.com/kb/shopify-microdata-for-google-shopping/

Two separate things are happening here and both are fixable.

First, the reason your metafields never show up: customAttributes is not a real schema.org property. Google only reads properties that exist in its vocabulary and silently drops everything else. So strain, thcPercentage, strainEffects and the rest are being stripped on parse because there’s no schema.org equivalent for them in Product or Offer. No formatting change will make Google pick those up in rich results, because the vocabulary simply doesn’t have a place for cannabis-specific attributes. They’re fine to keep for your own internal use but they won’t surface in Google rich results.

Second, the variant escaping is likely breaking your JSON silently. You’re wrapping metafield values in manual quotes like “{{ variant.metafields.custom.strain }}”. The moment one of those values contains an apostrophe, quote or line break (strain_description almost certainly does), the JSON becomes invalid and Google falls back to reading just the first valid Offer. That’s why you’re seeing only one variant. Let Liquid handle the escaping with the json filter instead of manual quotes:

liquid

"sku": {{ variant.sku | json }},
"price": "{{ variant.price | money_without_currency | replace: ',', '.' }}",
"availability": "https://schema.org/{% if variant.available %}InStock{% else %}OutOfStock{% endif %}"

Notice sku uses {{ variant.sku | json }} with no surrounding quotes. The json filter adds the quotes and escapes anything inside safely. Do that for every value that comes from a metafield or free text field.

Third, if you actually want Google to recognize each variant as a distinct item rather than collapsing them into a price range, the offers array isn’t the right structure. You need the ProductGroup type with a hasVariant array, where each variant is its own Product with a unique @id. The same-id collapse issue is a known thing with Shopify’s variant output.

For context, there’s an app called Webrex: AI SEO Schema, JSON-LD that handles the ProductGroup and variant structure automatically if you’d rather not maintain this by hand, but the json filter fix above will solve the immediate problem of variants not being detected.