Product schema for shopping agents
Part of the Agent Ready guides. Plain advice, no signup needed.
Product schema is structured data embedded in your pages that describes each product in a fixed format machines can read without guessing. Shopping agents read it to answer the questions shoppers ask: what is it, how much does it cost, is it in stock, how fast does it ship, and can it be returned. When the schema is complete and matches the visible page, the assistant can quote your product with confidence. When it is missing or wrong, the assistant quotes a competitor instead.
The format agents expect is JSON-LD, a script block of type application/ld+json placed in the page. It follows the Schema.org vocabulary, which is the shared dictionary used by search engines and assistants alike. You do not need to learn the whole vocabulary. A product page needs one Product block with an Offer inside it, plus review, shipping, and returns details as your catalog allows.
The minimum that works
Below is the smallest Product block worth shipping. It names the product, shows one offer with currency, price, availability, and a buy URL, and names the brand. Every field here earns its place: name and image identify the product, price and currency answer the cost question, availability answers the stock question, and url gives the assistant somewhere to send the shopper.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Harbor Canvas Tote",
"image": "https://yourstore.com/images/harbor-tote.jpg",
"description": "Heavy canvas tote with leather handles, fits a 14 inch laptop.",
"sku": "HT-001",
"brand": { "@type": "Brand", "name": "Harbor Goods" },
"offers": {
"@type": "Offer",
"url": "https://yourstore.com/products/harbor-canvas-tote",
"priceCurrency": "USD",
"price": "48.00",
"availability": "https://schema.org/InStock"
}
}
</script>Note the availability value is a full URL, not the word InStock on its own. Schema.org defines a fixed set: InStock, OutOfStock, PreOrder, BackOrder, LimitedAvailability, and Discontinued. Always use the full URL form. A bare word is ignored, which reads the same as having no stock data at all.
The full version assistants prefer
The minimum gets you read. The full version gets you recommended, because shipping and returns are the next questions every assistant asks before sending a shopper to checkout. Add shippingDetails with cost and delivery time, hasMerchantReturnPolicy with the return window, aggregateRating once you have real reviews, and identifiers such as gtin or mpn when your products carry them.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Harbor Canvas Tote",
"image": "https://yourstore.com/images/harbor-tote.jpg",
"description": "Heavy canvas tote with leather handles, fits a 14 inch laptop.",
"sku": "HT-001",
"gtin": "00812345678901",
"brand": { "@type": "Brand", "name": "Harbor Goods" },
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.7",
"reviewCount": "132"
},
"offers": {
"@type": "Offer",
"url": "https://yourstore.com/products/harbor-canvas-tote",
"priceCurrency": "USD",
"price": "48.00",
"availability": "https://schema.org/InStock",
"shippingDetails": {
"@type": "OfferShippingDetails",
"shippingRate": { "@type": "MonetaryAmount", "value": "4.95", "currency": "USD" },
"deliveryTime": {
"@type": "ShippingDeliveryTime",
"handlingTime": { "@type": "QuantitativeValue", "minValue": "1", "maxValue": "2", "unitCode": "DAY" },
"transitTime": { "@type": "QuantitativeValue", "minValue": "3", "maxValue": "5", "unitCode": "DAY" }
}
},
"hasMerchantReturnPolicy": {
"@type": "MerchantReturnPolicy",
"returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
"merchantReturnDays": 30,
"returnFees": "https://schema.org/FreeReturn"
}
}
}
</script>Adapt the numbers to your real policy. If returns cost the buyer postage, use ReturnFeesCustomerResponsibility instead of FreeReturn. If the window is 14 days, write 14. Assistants quote these values to shoppers, so every value here is a promise your support team will be asked to keep.
Variants done right
Products with sizes or colors need one offer per variant, each with its own availability and its own URL or variant identifier. A shirt with three sizes in stock and two out of stock should say exactly that, per size. A single offer marked InStock on a page where the selected size is sold out is the mismatch that burns trust fastest, because the shopper arrives ready to buy and finds otherwise.
Keep the variant data in sync with the variant picker. When the shopper selects a size, both the visible price and the schema should agree on what that size costs and whether it is available. Test this by selecting each variant and viewing the page source or the markup test output per variant URL. Many themes only emit schema for the default variant and never update it, which leaves every other variant undescribed.
Reviews without fakery
aggregateRating belongs on the page only when the reviews are real, collected from your own buyers, and visible on the same page. The ratingValue is the average, and reviewCount is the count of reviews that produced it. Both must match what the page shows. Marking up a 4.8 average while the page shows three reviews, or marking up reviews that appear nowhere on the page, is the kind of mismatch that gets markup ignored.
Collect reviews before you mark them up. A reviews app that gathers verified buyer reviews gives you both the visible reviews and the markup in one step. Do not invent counts to fill the field. An honest low count beats a fabricated high one, because assistants that cross check your feed, your page, and your review platform will find the gap.
Sale prices and currency
When a product is on sale, the price field carries the current selling price, the one the shopper pays today. Some merchants also add a compare at price using validUntil and a priceSpecification, but the plain price must always be the live price. A stale sale price in markup that disagrees with the page is treated as an error, not a bargain.
priceCurrency uses the three letter code: USD, EUR, GBP, and so on. If you sell in several currencies, the markup on each regional page or variant should carry that region currency and matching price. One currency in markup with another on the page reads as a mismatch. Stores with VAT included pricing should make sure the marked price equals the displayed price, tax treatment included.
Common mistakes that void your markup
Two Product blocks on one page describing the same product differently. This happens when the theme emits one block and an app emits another. Consumers of the data pick one, often the wrong one, or discard both. Audit the page source, count the Product blocks, and keep exactly one per product. Disable the duplicate at its source rather than hiding it.
Prices that disagree with the page. Feed price, markup price, and visible price must all match to the cent. Check sale items and currency converted pages first, since those are where drift starts. Recheck after every price change workflow, manual or automatic.
Availability that never changes. Pages hard coded to InStock keep saying so through stockouts. Tie availability output to live inventory state so the markup flips to OutOfStock or BackOrder the moment stock does. If you accept backorders, say BackOrder explicitly instead of leaving InStock up.
Markup for content the page does not show. Every name, price, rating, and policy in the block should be verifiable by a human reading the page. Hidden markup is the fastest route to having all of your markup distrusted.
Broken JSON. One missing comma invalidates the whole block. After every template change, run the page through a validator before publishing. Keep a known good product page as your reference and compare against it.
On Shopify
Most Shopify themes already print a basic Product block with name, price, currency, and availability. The fields usually missing are shippingDetails, hasMerchantReturnPolicy, and aggregateRating. Run an audit first and read the schema section so you fix only what is actually missing.
Without code, fill the gaps with apps. A schema app such as Schema Plus for SEO can add shipping and returns output. A reviews app such as Judge.me or Loox collects real reviews and adds aggregateRating markup. Set your shipping and refund policies under Settings, Policies first, so the markup points at real policy text.
With code, edit the product template under Online Store, Themes, Edit code. Add a JSON-LD block that reads the product and variant objects for price, sku, and inventory state. Test on one product, validate the output, then roll it out. Keep the theme block or the app block, not both, to avoid duplicates.
On WooCommerce
WooCommerce itself emits basic Product markup, and Rank Math or Yoast extend it. Pick one source of markup and disable the others. Two plugins each emitting Product blocks is the most common WooCommerce duplication, and it takes one settings toggle to fix.
Map your product data carefully. SKUs should be unique per product, GTINs entered where the brand provides them, and stock status tied to actual inventory. Variable products need per variation data enabled in your schema plugin so each size or color carries its own availability. Check a variable product with one sold out variation and confirm the output differs per variation.
Shipping and returns need explicit setup. Enter your shipping zones and rates in WooCommerce settings, write the returns policy on a real page, and confirm the schema plugin outputs both. If your plugin version lacks return policy output, add it with a small code snippet in the product template rather than leaving the field empty across the catalog.
How to validate
Validate every template change with two free tools. The Schema Markup Validator checks that your JSON-LD parses and uses the vocabulary correctly. The Rich Results Test shows which search features your markup qualifies for. Neither tool proves an assistant will recommend you, but both catch the syntax and vocabulary errors that guarantee it will not.
Validate per template, not per product. One product page, one variable product, one sale item, one out of stock item. If all four validate and match their pages, the template is sound and the catalog follows. Revalidate after theme updates, plugin updates, and any change to pricing or review apps.
Want to know which schema fields your store is missing today. Run the free Agent Ready audit to get a graded score and the exact fixes that matter most for your store. It takes under a minute and needs no signup.
Get a free AI-crawler audit of your store
Enter your email and store address and we will keep you posted on your score. After signup you can run the audit right away.
More guides: ChatGPT Shopping Shopify schema robots.txt for AI AI-crawler friendly Product schema llms.txt AI bot rules FAQ schema