Your message has been sent.

Which schema.org markup puts a product's price and availability into an assistant answer?

Put a Product node on the product page, with an Offer that carries price, priceCurrency, and availability. Render that JSON-LD in the server HTML, and keep those three values identical to the product feed. A price that exists only after JavaScript runs is not in the HTML a crawler saved.

The nodes on the product page

schema.org Product is the item. schema.org Offer is the commercial terms: the price, the currency, and whether it can be bought. schema.org AggregateRating is the summary of reviews, and it is optional.

The definitions are at https://schema.org/Product, https://schema.org/Offer, and https://schema.org/AggregateRating. Put the JSON-LD in the HTML of the product URL, not only on the category page.

Google publishes a separate check for merchant listings at https://developers.google.com/search/docs/appearance/structured-data/merchant-listing and a product-snippet page at https://developers.google.com/search/docs/appearance/structured-data/product. Those pages are the list Google currently validates. They are not a promise that ChatGPT, Perplexity, or Gemini read the same fields.

That merchant-listing page requires Product.name, image, and a nested Offer, not an AggregateOffer. The Offer needs a price greater than zero, as price or as priceSpecification.price, and the matching priceCurrency. availability is recommended there.

Offer: price, currency, availability

price is the number a buyer pays, in major units, with a decimal point. 18.00 is eighteen, not one thousand eight hundred. Do not put a currency symbol inside price.

priceCurrency is the ISO 4217 code, such as PLN, EUR, or USD. It is a separate field. A price of 18.00 and a priceCurrency of PLN is the pair. A single string such as 18.00 PLN belongs in a feed file, not in these two fields.

availability is an ItemAvailability URL. In stock is https://schema.org/InStock. Out of stock is https://schema.org/OutOfStock. Pre-order is https://schema.org/PreOrder. Backorder is https://schema.org/BackOrder. Use that full URL. Google's merchant-listing page also accepts the short name InStock. A shop status such as instock is neither.

url on the Offer should be the product page, with the variant selected when the price belongs to one variant. itemCondition can be https://schema.org/NewCondition when the item is new. priceValidUntil is optional and does not replace a feed update.

An AggregateOffer, with lowPrice and highPrice, describes a range. It does not say which variant is in stock. Use it only when you mean a range. For one sellable variant, use one Offer.

Product and AggregateRating

Product.name is the name on the page. image is a direct image URL, and it can be repeated. description is plain text. sku is your identifier. brand is a Brand node with name, or text. offers points at the Offer, or at an array of Offers when several variants are real offers.

AggregateRating.ratingValue is the average. reviewCount is how many reviews that average came from. ratingCount is accepted by schema.org as the other count. bestRating defaults to 5 when you omit it. Do not emit a rating when there are no reviews. Do not add store reviews into the product count.

The same facts can sit in the OpenAI product file as star_rating and review_count. Those feed fields are optional there. If the page and the file both state a rating, they should describe the same reviews.

The feed and the page are two copies

Google Merchant Center takes its own feed. The product data specification uses id, title, description, link, image_link, availability, price, and brand. The price there looks like 18.00 PLN. availability uses in_stock, out_of_stock, preorder, and backorder. The reference is https://support.google.com/merchants/answer/7052112.

The OpenAI discovery file uses item_id, url, image_url, and price as 18.00 PLN, and it spells pre-order as pre_order. seller_name is required there and has no twin in the basic Google row.

The page Offer, the Google feed, and the OpenAI feed are three copies of one fact. When they disagree, you do not control which copy an assistant repeats.

OpenAI's merchant page says a feed is optional when ChatGPT already crawls the site, and that a feed gives more control over how products appear. It does not say the feed wins when the two prices differ. Perplexity, Gemini, and Google AI Overviews are not that feed. Google's AI-features page says structured data should match the visible text.

A complete example

This is one product, one offer, server-rendered in the page. Replace the example.com URLs. The price has no currency symbol. availability is a full schema.org URL.

If the mug has a blue variant and a white variant at different prices, do not leave both prices out of the HTML and swap them with a script. Either give each variant its own URL and its own Offer, or list one Offer per variant in the offers array, each with its own price, availability, and url.

{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Ceramic mug, 350 ml",
  "image": "https://example.com/images/mug.jpg",
  "description": "Glazed ceramic mug with a handle. Dishwasher safe.",
  "sku": "MUG-350",
  "brand": { "@type": "Brand", "name": "Northline" },
  "offers": {
    "@type": "Offer",
    "url": "https://example.com/products/mug",
    "priceCurrency": "PLN",
    "price": "72.00",
    "availability": "https://schema.org/InStock",
    "itemCondition": "https://schema.org/NewCondition"
  },
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.6",
    "reviewCount": "28"
  }
}

How to check it

Open the product URL in Google's Rich Results Test: https://search.google.com/test/rich-results. It fetches the URL, or you can paste the HTML. Read the Product item. Confirm price, priceCurrency, and availability are the values on the page today, not an old template default.

The schema.org validator at https://validator.schema.org/ checks the vocabulary. A valid graph can still be the wrong price. Compare the extracted price with the price a buyer sees.

Also request the raw HTML. curl the product URL, or use view-source, and search for the price string and for schema.org/InStock. If they appear only after the page hydrates, the first response did not contain them.

What breaks the price

A price written only by client-side JavaScript is missing from the first HTML. Google documents that Googlebot renders JavaScript: https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics. That document is about Googlebot. It does not say the same for every AI crawler.

OpenAI, Perplexity, and Anthropic do not document a JavaScript renderer for GPTBot, OAI-SearchBot, ChatGPT-User, PerplexityBot, or ClaudeBot. Put the price in the HTML the server sends.

A mismatch with the feed is the next break. The page says 72.00 PLN and the file says 79.99 USD, or the page is in stock while the file says out_of_stock. Fix the source both copies read. Do not patch only the markup.

Variations break it when one URL serves many prices and the Offer names only the first. A color selected in the browser that never changes the server HTML leaves the assistant with the default variant. Give the variant a stable URL, and point the Offer url at it.

Other silent failures: availability as a shop word instead of a schema.org URL, price with a comma decimal, the Offer nested on the category page and missing on the product URL, and an AggregateRating with a ratingValue and a reviewCount of zero.

OpenAI documents about 24 hours for a robots.txt change to affect search. It does not publish a delay for a corrected price. Upload a full snapshot at least once a day, then check the same SKU again.

  • The product URL HTML contains a Product and an Offer before any script runs.
  • price is a decimal in major units, and priceCurrency is the ISO code.
  • availability is https://schema.org/InStock or the matching out-of-stock, pre-order, or backorder URL.
  • Each variant with its own price has its own Offer, or its own URL.
  • AggregateRating is omitted when there are no reviews.
  • The Offer price matches the Google Merchant price and the OpenAI feed price.
  • Rich Results Test shows the same price the buyer sees.
Is JSON-LD required, or is Microdata enough?

schema.org allows more than one syntax. JSON-LD in the server HTML is the form that is easy to diff against the feed. The test that matters is whether the price and availability are in the fetched HTML, under names a parser can read.

Should the category page carry an Offer for every product?

The offer belongs on the URL where that product is sold. A category grid can repeat names. It should not be the only place the price exists. Assistants cite the product URL.

What availability URL do I use when the item is out of stock?

https://schema.org/OutOfStock. Leave the Offer in the page, with the current price. Removing the Offer does not say out of stock. It says nothing.

Does AggregateRating change the price an assistant quotes?

No. The price comes from Offer.price and priceCurrency. AggregateRating is the review summary. Omit it when reviewCount would be zero.