Magento 2 Product JSON-LD Schema for AI Search — 2026 Guide

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 emits Product microdata (name, image, price, availability) but no JSON-LD and no sku, brand, GTIN, shipping or return fields.
  • 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. Google recommends JSON-LD, and it is the format AI systems parse most reliably, including OAI-SearchBot (ChatGPT) and Googlebot, which feeds AI Overviews and Gemini. 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 — What the Microdata Covers and What It Misses

If your store runs on Hyvä Theme, the product page <body> is a schema.org/Product microdata item: the title carries name, the gallery image, and the price block an Offer with price, currency and availability. What Hyvä does not emit is JSON-LD, and the fields shopping surfaces use to match and trust a product: sku, brand, GTIN, shippingDetails and hasMerchantReturnPolicy. Details: what Hyvä emits as product schema.

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 1+ on Hyvä: Product microdata on , Offer inside it

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 the full 20-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. Crawlers that run little or no JavaScript, such as OAI-SearchBot and PerplexityBot, are likely to miss it. Googlebot renders JavaScript, so results differ between systems. If you use GTM for schema today, move it into the server-rendered page.

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 emits Product microdata but no JSON-LD and no brand, GTIN, shipping or return fields.

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?

Not reliably. OAI-SearchBot and PerplexityBot run little or no JavaScript, so they are likely to miss schema added by GTM. Googlebot renders JavaScript, so Google may see it while others do not. 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 the 20 signals the audit checks. 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

More on this topic