TL;DR — 2 minute version
  • Default Magento 2 outputs microdata — not JSON-LD. AI engines prefer JSON-LD.
  • The single most common failure: offers.availability is missing — ChatGPT Shopping skips your product entirely.
  • AI crawlers do not execute JavaScript — schema via GTM is invisible to OAI-SearchBot and PerplexityBot.
  • Hyvä Theme has zero Product schema by default — neither microdata nor JSON-LD.
  • This guide gives you the copy-paste fix, Magento-specific pitfalls, Hyvä solution, and a validation checklist.
How to implement Product JSON-LD schema in Magento 2 for AI search engines, ChatGPT visibility and structured data optimization (2026 guide)

Your Magento store might rank on page one in Google — and still be invisible to ChatGPT, Gemini, and Perplexity. In most cases, the reason is not robots.txt or llms.txt. It is Product schema.

AI search engines need structured product data to answer purchase queries. When they cannot parse your product data from JSON-LD, they skip your store and recommend a competitor whose schema is correct. This guide covers exactly what is missing, why, and how to fix it.

Before vs After: What Schema Visibility Looks Like

SetupChatGPT ShoppingGemini product resultsPerplexity citations
No schema❌ Invisible❌ Invisible❌ Invisible
Microdata only (Luma default)⚠️ Partially parsed⚠️ Partially parsed⚠️ Inconsistent
JSON-LD, no offers.availability❌ Feed validation fails⚠️ May appear⚠️ May appear
Full JSON-LD + availability + rating✅ Eligible for Shopping✅ Product results✅ Cited with price

Why Magento 2 Fails AI Schema by Default

Default Magento 2 (Luma theme) outputs microdata — the older itemscope/itemprop HTML attribute format:

<!-- Default Magento Luma — NOT sufficient for AI search -->
<div itemscope itemtype="http://schema.org/Product">
  <span itemprop="name">Product Name</span>
  <div itemprop="offers" itemscope itemtype="http://schema.org/Offer">
    <span itemprop="price">49.99</span>
    <!-- offers.availability missing — AI cannot confirm purchasability -->
  </div>
</div>

Three problems with this for AI search:

1. It is microdata, not JSON-LD. Modern AI crawlers including OAI-SearchBot (ChatGPT) and Google-Extended (Gemini) prefer JSON-LD. Microdata is embedded in HTML that themes and extensions modify — it breaks easily and silently.

2. offers.availability is missing. This is the single most common failure. Without explicit availability set to a schema.org URI, ChatGPT Shopping feed validation fails automatically.

3. AI crawlers do not execute JavaScript. Schema injected via Google Tag Manager or any JavaScript that runs after initial page load is completely invisible to AI crawlers. Schema must be server-side rendered in the HTML source.

Critical: If you use GTM to inject schema, AI engines cannot see it. This is confirmed by how OAI-SearchBot and PerplexityBot work — they parse the raw HTML response, not the rendered DOM. This is the most overlooked Magento AEO mistake.

The Complete Copy-Paste Product JSON-LD

This is the spec-compliant JSON-LD that passes all AEO audit checks. Copy and adapt with your product data:

<script type="application/ld+json">
{
  "@context": "https://schema.org/",
  "@type": "Product",
  "name": "Alpine Hiking Jacket",
  "description": "Waterproof 3-layer shell for alpine conditions. 300g weight, packed size 18x10cm.",
  "sku": "WB-004",
  "image": [
    "https://yourstore.com/media/catalog/product/jacket-front.jpg",
    "https://yourstore.com/media/catalog/product/jacket-back.jpg"
  ],
  "brand": {
    "@type": "Brand",
    "name": "TrailMaster"
  },
  "offers": {
    "@type": "Offer",
    "priceCurrency": "EUR",
    "price": "189.99",
    "priceValidUntil": "2026-12-31",
    "availability": "https://schema.org/InStock",
    "itemCondition": "https://schema.org/NewCondition",
    "url": "https://yourstore.com/alpine-hiking-jacket.html",
    "seller": {
      "@type": "Organization",
      "name": "Your Store Name"
    }
  },
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.6",
    "reviewCount": "38",
    "bestRating": "5",
    "worstRating": "1"
  }
}
</script>

Required Fields — What AI Engines Actually Need

FieldRequired for AI?Common mistake
@type: Product✅ RequiredUsing IndividualProduct — both work, Product is preferred
name✅ RequiredIncluding HTML tags in the name string
offers.availability✅ RequiredText string instead of URI (see table below)
offers.price✅ RequiredIncluding currency symbol: "€189" instead of "189.99"
offers.priceCurrency✅ RequiredUsing symbol instead of ISO code EUR
image⚠️ Strongly recommendedRelative URL instead of absolute
sku⚠️ Strongly recommendedOften missing entirely
aggregateRating⚠️ Strongly recommendedIncluding when reviewCount is 0 — this causes validation error
brand⚠️ RecommendedPlain string instead of {"@type": "Brand", "name": "..."}
description⚠️ RecommendedDuplicate of page title instead of unique description

offers.availability — Correct vs Wrong Format

Stock status✅ Correct value❌ Wrong (silently rejected)
In stockhttps://schema.org/InStock"In Stock" / "instock" / "available"
Out of stockhttps://schema.org/OutOfStock"Out of Stock" / "unavailable"
Pre-orderhttps://schema.org/PreOrder"preorder" / "coming soon"
Back orderhttps://schema.org/BackOrder"backorder"

AI parsers require the full schema.org URI. Text strings are parsed as unknown values and treated as missing. No error is shown — your product just doesn’t appear.

Configurable Products — Correct Structure

Default Magento flattens configurable product schema. If 6 of 8 size variants are in stock and 2 are not, AI engines may see “availability unclear” and suppress the listing. Correct structure uses AggregateOffer with per-variant Offer objects:

{
  "@type": "Product",
  "name": "Running Shoe",
  "offers": {
    "@type": "AggregateOffer",
    "offerCount": 8,
    "lowPrice": "89.99",
    "highPrice": "89.99",
    "priceCurrency": "EUR",
    "availability": "https://schema.org/InStock",
    "offers": [
      {
        "@type": "Offer",
        "sku": "RS-42-BLK",
        "name": "Size 42 / Black",
        "availability": "https://schema.org/InStock",
        "price": "89.99",
        "priceCurrency": "EUR"
      },
      {
        "@type": "Offer",
        "sku": "RS-43-BLK",
        "name": "Size 43 / Black",
        "availability": "https://schema.org/OutOfStock",
        "price": "89.99",
        "priceCurrency": "EUR"
      }
    ]
  }
}

Hyvä Theme — Why Schema Is Completely Missing

If your store runs on Hyvä Theme, there is zero Product schema by default — neither microdata nor JSON-LD. Hyvä is built on Alpine.js and does not include the Luma-based product template that generates microdata.

Verify:

curl -s https://yourstore.com/sample-product.html | grep -c 'application/ld+json'
# Returns 0 → no JSON-LD at all

curl -s https://yourstore.com/sample-product.html | grep -c 'itemscope'
# Returns 0 → no microdata either

The fix requires injecting JSON-LD via layout XML. Create Vendor_Theme/layout/catalog_product_view.xml:

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <head>
        <block class="Magento\Framework\View\Element\Template"
               name="product.json.ld"
               template="Vendor_Module::product/json-ld.phtml"/>
    </head>
</page>

Note: JSON-LD injected via <head> in layout XML is server-side rendered and visible to AI crawlers. This is exactly where it needs to be.

Or use angeo/module-rich-data which handles both Luma and Hyvä automatically without any template editing.

How to Diagnose Your Current Schema

Run this one-liner to see exactly what schema your product pages output:

# Replace with a real product URL from your store
curl -s https://yourstore.com/sample-product.html | \
  python3 -c "
import sys, json, re
body = sys.stdin.read()
blocks = re.findall(r'<script[^>]+type=\"application/ld\+json\"[^>]*>(.*?)</script>', body, re.DOTALL)
for b in blocks:
    try:
        d = json.loads(b)
        items = d if isinstance(d, list) else [d]
        for item in items:
            nodes = item.get('@graph', [item])
            for n in nodes:
                if n.get('@type') in ('Product', 'IndividualProduct'):
                    print('FOUND Product schema')
                    offers = n.get('offers', {})
                    if isinstance(offers, list): offers = offers[0] if offers else {}
                    print('availability:', offers.get('availability', 'MISSING'))
                    print('price:', offers.get('price', 'MISSING'))
                    print('priceCurrency:', offers.get('priceCurrency', 'MISSING'))
                    print('aggregateRating:', 'present' if n.get('aggregateRating') else 'MISSING')
    except: pass
  "

Four possible outputs:

OutputMeaningAction
No outputNo Product schema at allInstall angeo/module-rich-data
availability: MISSINGSchema exists, offers missingFix offers block
availability: InStockWrong format (text string)Change to full URI
availability: https://schema.org/InStockCorrectRe-run CLI audit to confirm

Or use the CLI audit module for a full 9-signal AEO check:

composer require angeo/module-aeo-audit
bin/magento setup:upgrade && bin/magento cache:flush
bin/magento angeo:aeo:audit

Implementation Options

Option 1 — Module (recommended, 5 minutes)

composer require angeo/module-rich-data
bin/magento setup:upgrade && bin/magento cache:flush

The angeo/module-rich-data module injects server-side JSON-LD for all product pages automatically. Works on Luma and Hyvä. Includes Product, Organization, BreadcrumbList, WebSite, and FAQPage schema. MIT licensed, free.

Option 2 — Manual phtml template

Create app/design/frontend/Vendor/Theme/Magento_Catalog/templates/product/json-ld.phtml:

<?php
$product = $block->getProduct();
$schema = [
    '@context' => 'https://schema.org/',
    '@type'    => 'Product',
    'name'     => $product->getName(),
    'sku'      => $product->getSku(),
    'offers'   => [
        '@type'         => 'Offer',
        'price'         => (string) $product->getFinalPrice(),
        'priceCurrency' => $block->getCurrencyCode(),
        'availability'  => $product->isAvailable()
            ? 'https://schema.org/InStock'
            : 'https://schema.org/OutOfStock',
        'itemCondition' => 'https://schema.org/NewCondition',
    ],
];
?>
<script type="application/ld+json">
<?= json_encode($schema, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?>
</script>

Option 3 — GTM

Not recommended for AEO. GTM injects schema after page load via JavaScript. AI crawlers parse raw HTML and do not execute JavaScript — schema injected via GTM is completely invisible to OAI-SearchBot, PerplexityBot, and Google-Extended. If you currently use GTM for schema, you have no AI-visible schema regardless of what Google Tag Manager shows.

Validation Checklist

  • ☐ JSON-LD format (not microdata or GTM-injected)
  • offers.availability set to full schema.org URI
  • offers.price is a number string without currency symbol
  • offers.priceCurrency is ISO 4217 code (EUR, USD, GBP)
  • image contains at least one absolute URL
  • aggregateRating omitted if reviewCount is 0
  • ☐ Schema server-side rendered (visible in curl output)
  • ☐ Hyvä: schema injected via layout XML <head>
  • ☐ Configurable products use AggregateOffer with per-variant Offer
  • ☐ Validated at validator.schema.org — zero errors

FAQ

Does Magento 2 support Product JSON-LD out of the box?

No. Default Luma outputs microdata (the older itemscope/itemprop format), not JSON-LD. Additionally, the default microdata is missing offers.availability. Hyvä Theme stores have zero Product schema by default — neither microdata nor JSON-LD.

Is offers.availability required for ChatGPT Shopping?

Yes. ChatGPT Shopping feed validation fails automatically when offers.availability is missing or set to a text string. The value must be a full schema.org URI: https://schema.org/InStock.

Does ChatGPT actually read JSON-LD from my Magento store?

Yes, via OAI-SearchBot. It parses the raw HTML response of your product pages. However, it does not execute JavaScript — schema injected via GTM or any JS that runs after page load is invisible. Schema must be server-side rendered.

Does GTM-injected schema work for AI search?

No. AI crawlers do not execute JavaScript. Schema injected via GTM is invisible to OAI-SearchBot, PerplexityBot, and Google-Extended. This is one of the most common AEO mistakes — a store passes Google Rich Results Test but has zero AI-visible schema because it all goes through GTM.

Why does Hyvä Theme break Product schema?

Hyvä removes Luma’s product templates entirely to rebuild them in Alpine.js. The microdata that Luma injects via phtml templates is gone. You need to add JSON-LD separately via layout XML or a module like angeo/module-rich-data.

Does Google Rich Results Test passing mean my schema is correct for AI?

Not fully. Google Rich Results validates schema for Google Search features like rich snippets. AI search engines (ChatGPT Shopping specifically) require additional fields — particularly offers.availability as a full URI — that Google does not enforce but OpenAI does. Always verify with the CLI audit tool, not just Google’s test.


What’s Next

Product schema is one of 9 AEO signals. After fixing schema, the next highest-impact signals are:

Run the full audit to see your current score across all signals:

bin/magento angeo:aeo:audit

Or check any store URL without installing: angeo.dev/ai-magento-audit


0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *