Mark up each product page with Product and a single Offer whose name, price, currency, availability and identifiers match what a shopper can see on that page. Then validate twice. First check syntax and Google’s required properties. Then compare every marked-up value with the visible text, one by one.

Google says there’s “no special schema.org structured data” needed for its AI features, and it doesn’t guarantee rich results either. Rich results and AI choices are separate outcomes, and markup can’t promise either one.

What markup can do is put the same facts in a second, machine-readable place. Its job is to agree with the page.

Three outcomes people mix up

Most arguments about product schema blur three different questions.

Question What the evidence says Source
Is the markup valid and true? Within your control. Google publishes required properties and says markup must describe content visible on the page Google’s merchant listing and general guidelines pages
Will Google show a rich result? Not guaranteed. Google says it “does not guarantee” structured data features will show, and may try to verify merchant data first Google’s general guidelines and merchant listing pages
Will an AI assistant pick or cite the product? Not documented as a markup effect. Google says no special markup is needed for AI features. One vendor study found adding JSON-LD barely moved AI citations on pages already cited Google’s AI features page; the Ahrefs study

Keep that third row honest in both directions. Ahrefs tracked 1,885 pages that added JSON-LD and compared them with matched control pages. Adding JSON-LD produced “no major uplift” in citations in AI Overviews, AI Mode or ChatGPT. But every page studied was already heavily cited, all schema types were pooled together, and the window was 30 days.

Ahrefs itself says there are still good reasons to use schema, rich results among them. A near-null citation result for pages that are already visible doesn’t show that Product markup is useless on a product page. It shows the markup isn’t a shortcut to AI citations.

On the shopping side, OpenAI says ChatGPT’s shopping results draw on “structured metadata from first-party and third-party providers (e.g., price, product description)”. It doesn’t say whether that includes JSON-LD on your page. Google’s Shopping help says its “Top recommendations” are selected based on relevance to the search, ratings, price and product features. Markup isn’t named as an input. Google says it combines structured data with Merchant Center feeds where both exist. The common thread is consistency. Several systems may hold copies of your facts, and the copies should agree. Our price and stock guide covers keeping page, feed and markup in step.

The annotated example

Hypothetical store and product, not a real listing. The page shows:

  • the title “Top-fill humidifier, 4 L, white”
  • “$69.99 was $89.99”
  • “In stock”
  • a one-line description
  • a link to the store’s returns policy

Here’s the Product markup for that page, with notes:

{
  "@context": "https://schema.org/",
  "@type": "Product",
  "name": "Top-fill humidifier, 4 L, white",
  "description": "Top-fill cool-mist humidifier with a 4 L tank.",
  "image": [
    "https://example.com/images/hum4-white-1x1.jpg",
    "https://example.com/images/hum4-white-4x3.jpg",
    "https://example.com/images/hum4-white-16x9.jpg"
  ],
  "sku": "HUM4-WHT",
  "brand": { "@type": "Brand", "name": "Example Home" },
  "offers": {
    "@type": "Offer",
    "url": "https://example.com/products/top-fill-humidifier?variant=white",
    "price": 69.99,
    "priceCurrency": "USD",
    "priceSpecification": {
      "@type": "UnitPriceSpecification",
      "priceType": "https://schema.org/StrikethroughPrice",
      "price": 89.99,
      "priceCurrency": "USD"
    },
    "availability": "https://schema.org/InStock",
    "itemCondition": "https://schema.org/NewCondition"
  }
}
Property Note Must match on the page Rule source
name Required. Same words as the visible title, including the variant The H1 or product title Google merchant listing: required properties
image Required. Crawlable, and it must show the marked-up product. Google recommends several high-resolution images, in 16x9, 4x3 and 1x1 The product photos shown Google merchant listing: image guidelines
sku Your own ID. Fine to include Not usually visible. It’s an internal code Schema.org: a merchant-specific identifier
gtin Left out on purpose. This product has no assigned GTIN Nothing to match Our identifier guide
offers @type: Offer Required as a single Offer. An AggregateOffer doesn’t qualify for merchant listings The purchase box Google merchant listing: “the merchant has to be the seller”
price The active price, above zero, as a number “$69.99” Google merchant listing: price
priceSpecification with StrikethroughPrice The original price during a sale. Never mark the active price with a priceType “was $89.99” Google merchant listing: sale pricing
priceCurrency A three-letter ISO 4217 code, never “$” The currency shown Google merchant listing: priceCurrency
availability An ItemAvailability value “In stock” Google merchant listing: availability
url The page for this variant The URL the shopper lands on Google merchant listing: Offer

Two things are missing on purpose. First, there’s no aggregateRating, because the page shows no reviews, and Google’s rules say not to mark up content readers can’t see. Second, there’s no return policy in this block. Google recommends a store-wide return policy nested under Organization markup, and product-level policies only to override it. Our shipping and returns markup guide covers that block.

For a product with several variants, schema.org’s ProductGroup represents “a group of Products that vary only in certain well-described ways, such as by size, color, material”. The group itself isn’t offered for sale. Its variants are. Our variant guide covers that structure.

The first draft, and what was wrong with it

A first draft for the same hypothetical page looked like this, shortened:

{ "@type": "Product", "name": "Top-Fill Humidifier 4L", "gtin": "HUM4-WHT",
  "offers": { "@type": "AggregateOffer", "price": "89.99",
              "priceCurrency": "$", "availability": "https://schema.org/InStock" } }

We ran both blocks through a short script that encodes a subset of Google’s stated rules and compares values with the page text. The corrected block passed. The draft failed in five places, plus one problem the script couldn’t see:

Problem Why it matters Fix
AggregateOffer Merchant listings require a single Offer "@type": "Offer"
price “89.99” That’s the compare-at price. The active price the page shows is 69.99. Google lists price as a number 69.99, with 89.99 as StrikethroughPrice
priceCurrency “$” Must be ISO 4217 "USD"
gtin “HUM4-WHT” A SKU in the GTIN field fails the GTIN length and check-digit rules Remove it and keep the value in sku
name doesn’t match the title Markup should describe what readers see Copy the visible title
Stock state “In stock” is right today. If the template hard-codes it, the markup keeps saying it after the item sells out Generate it from live inventory

The price row is the costly one. The draft would have told any system reading the markup that the product costs $89.99, while the page charges $69.99. That’s exactly the kind of mismatch Google’s guidelines warn about. It’s also the kind a shopper would notice.

The validation checklist

Syntax and required properties

  • The JSON-LD parses, with no trailing commas or smart quotes.
  • Product has name, image and offers.
  • offers is one Offer with a price above zero and an ISO 4217 currency.
  • A sale uses StrikethroughPrice on the original price only.
  • Run the page through Google’s Rich Results Test and fix any critical errors. Then use URL Inspection on a few deployed pages. Google’s page describes both steps.

Visible parity. Do this by hand or by script, on the rendered page:

  • name matches the visible title.
  • price matches the price the shopper pays. The strikethrough price matches the “was” price shown.
  • availability matches the stock message for the selected variant.
  • Every identifier is real. No SKU sits in gtin or mpn.
  • No rating, review or policy is marked up unless the page shows it.

Delivery

  • The markup is in the initial HTML. Google recommends this, and warns that JavaScript-generated markup can make Shopping crawls “less frequent and less reliable” for fast-changing price and availability.
  • Price and stock come from the same source as the visible page, not a cached template.
  • Markup, Merchant Center feed and page agree. Google says it may combine them, and may try to verify merchant listing data before showing it.

What markup can’t carry for you

Markup repeats facts. It doesn’t create them. If the page never says the humidifier’s tank size, runtime or noise level, there’s nothing true to mark up. And Google’s rule against marking up invisible content means you shouldn’t put those facts in JSON-LD alone.

Put the facts a shopper asks about on the page first, in words and with units. Then let markup copy them. Our requirement-by-requirement check shows how to find which requests a page leaves unanswered.

How we checked

On October 27, 2026 we read Google’s Introduction to Product structured data (last updated 2025-12-10), Merchant listing structured data (last updated 2026-09-08), General structured data guidelines (last updated 2026-07-10) and AI features pages. We also read schema.org’s ProductGroup and MerchantReturnPolicy definitions, OpenAI’s shopping help article, and the Ahrefs study (published 2026-05-11, modified 2026-06-09). Every rule in the annotation and checklist is quoted or closely paraphrased from those pages.

The draft and corrected blocks were checked with a short script. It parses the JSON and tests required Product properties, Offer versus AggregateOffer, a price above zero, the ISO currency, priceType usage, GTIN length and check digit, and whether the name, prices and stock text appear in the page text we wrote for the example. It isn’t the Rich Results Test, and it doesn’t replace Google’s own tools.

Your next step

Open one product page, view the source, and find the application/ld+json block. Put it side by side with the rendered page and fill in the “Must match on the page” column of the annotation table for every property it contains. Anything that doesn’t match, or that the page doesn’t show at all, is the first fix. Then test the page in the Rich Results Test.