Skip to content
Blog

The schema fields AI agents actually read: AggregateRating, returns, shipping

2026-07-31

Schema

A Product node that passes a validator and a Product node an agent can act on are not the same object. The schema fields AI agents read are a narrow subset of what schema.org permits, and most stores emit the wrong subset: a name, an image, sometimes a price. All of it technically valid. Almost none of it useful to a program deciding whether to put you in front of a buyer.

This became a practical concern when the buying surface moved. Shopify turned agentic storefronts on for eligible merchants in March 2026 and has shipped llms.txt, agents.md and UCP support natively since May 2026, and AI-referred traffic and AI-attributed orders grew sharply through 2025 and 2026. The longer argument for why the shift landed when it did is in the switch was flipped. What matters here is the consequence: agents select merchants partly on structured data completeness, and in particular on AggregateRating, MerchantReturnPolicy, OfferShippingDetails and FAQPage.

Valid is not the same as useful

A validator checks shape. It asks whether your JSON parses, whether the types exist, whether required properties are present for the rich result you are targeting. It does not ask whether a buyer could make a decision from what you emitted. An agent does ask that, because it is holding two or three candidate listings and needs a reason to prefer one. Every field below exists to answer a specific question, which is a good test for anything else you consider adding. If you cannot name the buyer question a property answers, it is probably not worth the markup. That principle is the whole of agent readiness applied to one file.

AggregateRating

Answers: is it any good. This is the single field that lets an agent rank you against an otherwise identical competitor, and Sextant reports it as product.aggregate_rating.

{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Merino Base Layer",
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.6",
    "reviewCount": "184",
    "bestRating": "5"
  }
}

Emit only ratings you genuinely have. Inventing review counts is a policy violation on most platforms and it is trivially detectable, because the number in your markup has to survive comparison with the reviews visible on the page and with whatever the same product shows elsewhere. If you have no reviews yet, omit the property rather than fabricate one.

MerchantReturnPolicy

Answers: what happens if I need to send it back. Reported as merchant.return_policy. A returns page written in prose is effectively invisible here, because the agent needs discrete fields rather than a paragraph it has to interpret.

{
  "@context": "https://schema.org",
  "@type": "MerchantReturnPolicy",
  "applicableCountry": "GB",
  "returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
  "merchantReturnDays": 30,
  "returnMethod": "https://schema.org/ReturnByMail",
  "returnFees": "https://schema.org/FreeReturn"
}

returnPolicyCategory is an enumeration, not a sentence: finite window, unlimited window or not returnable. merchantReturnDays is only meaningful with the finite category. returnFees is where merchants quietly lose comparisons, since free returns and buyer-paid returns are a real difference an agent can act on. The field by field detail is in MerchantReturnPolicy schema.

OfferShippingDetails

Answers: when does it arrive and what does delivery cost. Reported as merchant.shipping_details. Three parts matter: the rate, the destination it applies to, and the time split into handling and transit.

{
  "@context": "https://schema.org",
  "@type": "OfferShippingDetails",
  "shippingRate": { "@type": "MonetaryAmount", "value": "4.95", "currency": "GBP" },
  "shippingDestination": { "@type": "DefinedRegion", "addressCountry": "GB" },
  "deliveryTime": {
    "@type": "ShippingDeliveryTime",
    "handlingTime": { "@type": "QuantitativeValue", "minValue": 0, "maxValue": 1, "unitCode": "DAY" },
    "transitTime": { "@type": "QuantitativeValue", "minValue": 1, "maxValue": 3, "unitCode": "DAY" }
  }
}

Handling time is the days between order and dispatch. Transit time is days in the carrier network. Agents add them to produce an arrival estimate, so collapsing both into a single vague range costs you accuracy. More on the structure in OfferShippingDetails schema.

FAQPage

Answers the long tail: the specific questions a product page leaves open. This is the field most often missing entirely, and Sextant reports it as merchant.faq_schema.

{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [{
    "@type": "Question",
    "name": "Does this fit a 15 inch laptop?",
    "acceptedAnswer": {
      "@type": "Answer",
      "text": "Yes. The main compartment fits laptops up to 15.6 inches."
    }
  }]
}

Write answers that stand alone. An agent may quote one without the surrounding page, so an answer that begins "as mentioned above" is worthless. See FAQPage schema for ecommerce for how to choose which questions to mark up.

SKU, GTIN and MPN

Answers: is this the same thing I saw somewhere else. These are the identity fields, checked as product.sku and product.gtin.

{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Merino Base Layer",
  "sku": "BW-ML-001",
  "gtin13": "5012345678900",
  "mpn": "BWML001",
  "brand": { "@type": "Brand", "name": "Bluewater" }
}

SKU is internal to you. GTIN is the barcode number shared by every seller of that item, and MPN is the manufacturer's part number. Without GTIN or MPN an agent has your listing as an unmatched item and has to fall back on fuzzy title matching. With them, you are a competing offer on a product it already understands, which is the starting point for being recommended rather than merely readable.

Price, currency and availability

Answers: what does it cost and can I have it. Easy to get wrong in two specific ways. Price without priceCurrency is a number with no unit. Availability written as free text forces a consumer to guess at your phrasing.

{
  "@context": "https://schema.org",
  "@type": "Offer",
  "price": "89.00",
  "priceCurrency": "GBP",
  "availability": "https://schema.org/InStock",
  "itemCondition": "https://schema.org/NewCondition",
  "url": "https://example.com/products/merino-base-layer"
}

Use the schema.org enumeration URL for availability: InStock, OutOfStock, PreOrder, BackOrder. These are product.offer_price and product.availability.

Read what you actually emit

Do not trust the theme or the plugin. Fetch the product page and read the JSON-LD yourself, either through view source or a plain HTTP request, and check that the fields above are present with real values rather than empty strings or template placeholders. Platforms differ sharply in what they give you by default: WooCommerce core emits no Product schema at all without a plugin, which is covered in WooCommerce product schema. Check again after any theme or plugin update, since these are exactly the files a deploy overwrites.

If you want to see which of these fields your product pages currently emit, run a free scan. It reads public pages only and takes under a minute.