Token-efficient product JSON-LD patterns

Lead product JSON-LD with the fields an agent can match and compare: identifiers (gtin, sku, mpn), price, availability, shipping, and returns. Use ProductGroup with hasVariant to keep multi-variant markup compact.

On this page
  1. Which fields carry decision weight
  2. Skeleton A: single product with shipping and returns
  3. Skeleton B: multi-variant products (ProductGroup)
  4. Before you ship
  5. Related blueprints and chapters

Version 1.1 · Last verified

This blueprint ships two verified Product JSON-LD skeletons you can copy, a single product with shipping and returns and a multi-variant ProductGroup, and it explains which fields an AI shopping agent actually reads to select you.

Key takeaways

  • The fields an agent can match and compare carry the selection weight: identifiers (gtin, sku, mpn), price, availability, shipping, and returns. Lead with those; brand prose can wait.
  • Two skeletons ship here verbatim, a single product with shipping and returns and a multi-variant ProductGroup. Copy one, swap in your data, then validate before shipping.
  • For products that vary by color, size, or material, Google documents ProductGroup with hasVariant as the compact representation, so shared fields are stated once instead of repeated on every variant.
  • The GTIN in the single-product skeleton is a checksum-valid placeholder, and neither skeleton has been run through a validator yet, so validate both and replace the GTIN before you ship.

Which fields carry decision weight

An agent does not read Product JSON-LD to admire your copy. It reads it to answer a shopper's query and to line you up against competing offers, so the fields worth optimizing are the ones it can match and compare.

Google's merchant listing documentation defines the Product and Offer core an agent reads: name, image, description, sku, mpn, a GTIN property (gtin13 for a 13-digit code, or the generic gtin), and brand, alongside an offers object carrying url, priceCurrency, price, priceValidUntil, availability, and itemCondition.Spec-factGoogle, Merchant listing (Product) structured data The availability value is written as a canonical full schema.org URL, for example https://schema.org/InStock (opens in new tab)(opens in new tab), drawn from the ItemAvailability enumeration that also includes OutOfStock, BackOrder, PreOrder, PreSale, SoldOut, LimitedAvailability, and Discontinued.Spec-factschema.org, ItemAvailability For itemCondition, Google lists NewCondition, RefurbishedCondition, and UsedCondition, again as full schema.org URLs.Spec-factGoogle, Merchant listing (Product) structured data

Those are the field definitions. The ordering judgment is ours. Among these fields, the ones that plausibly carry the most selection weight are the ones an agent can match against a query and compare across stores: the identifiers that resolve the product to a single global identity, the price and its currency, the availability that says whether it can transact now, and the shipping and return terms that break ties between otherwise equal offers. Free-text description and brand voice are the least comparable, so they earn the least token budget.Hypothesis (our analysis) This is the same facts-per-token discipline the handbook argues for on the page itself, applied to the structured layer. See information density on machine surfaces for the underlying case.

Skeleton A: single product with shipping and returns

Copy this block, swap in your real values, and keep the shape. It carries the identifiers, the offer, an offer-level shipping model, and a return policy in one object.

{
  "@context": "https://schema.org/",
  "@type": "Product",
  "name": "Example Widget",
  "image": ["https://example.com/photos/widget-1x1.jpg"],
  "description": "One-line factual product description.",
  "sku": "SKU-12345",
  "mpn": "MPN-98765",
  "gtin13": "9780201379624",
  "brand": { "@type": "Brand", "name": "ExampleBrand" },
  "offers": {
    "@type": "Offer",
    "url": "https://example.com/products/example-widget",
    "priceCurrency": "USD",
    "price": 29.99,
    "priceValidUntil": "2026-12-31",
    "availability": "https://schema.org/InStock",
    "itemCondition": "https://schema.org/NewCondition",
    "shippingDetails": {
      "@type": "OfferShippingDetails",
      "shippingRate": { "@type": "MonetaryAmount", "value": 3.49, "currency": "USD" },
      "shippingDestination": { "@type": "DefinedRegion", "addressCountry": "US" },
      "deliveryTime": {
        "@type": "ShippingDeliveryTime",
        "handlingTime": { "@type": "QuantitativeValue", "minValue": 0, "maxValue": 1, "unitCode": "DAY" },
        "transitTime": { "@type": "QuantitativeValue", "minValue": 1, "maxValue": 5, "unitCode": "DAY" }
      }
    },
    "hasMerchantReturnPolicy": {
      "@type": "MerchantReturnPolicy",
      "applicableCountry": "US",
      "returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
      "merchantReturnDays": 30,
      "returnMethod": "https://schema.org/ReturnByMail",
      "returnFees": "https://schema.org/FreeReturn"
    }
  }
}

The two nested blocks are where most catalogs are thin, and they are exactly the tie-breakers. Google's merchant listing documentation defines the offer-level shipping shape as offers.shippingDetails of type OfferShippingDetails, carrying a shippingRate (a MonetaryAmount with value and currency), a shippingDestination (a DefinedRegion whose addressCountry is an ISO 3166-1 alpha-2 code), and a deliveryTime (ShippingDeliveryTime) that splits into handlingTime and transitTime, each a QuantitativeValue with minValue, maxValue, and unitCode DAY.Spec-factGoogle, Merchant listing (Product) structured data Google's return policy documentation (last updated 2025-12-10) defines offers.hasMerchantReturnPolicy of type MerchantReturnPolicy, with applicableCountry, a returnPolicyCategory enum (MerchantReturnFiniteReturnWindow, MerchantReturnNotPermitted, or MerchantReturnUnlimitedWindow), returnMethod such as ReturnByMail, and returnFees such as FreeReturn; merchantReturnDays is an integer and is required when the category is a finite window.Spec-factGoogle, Return policy structured data

We lead this skeleton with the identifiers and the offer, then shipping, then returns, because an agent resolves what the product is before it weighs how the product ships and how returns work. JSON-LD key order means nothing to a parser, so this sequence is for the humans who maintain the file; the load-bearing decision is which fields you populate, not their order.Hypothesis (our analysis)

Skeleton B: multi-variant products (ProductGroup)

When a product varies by color, size, or material, do not emit one bloated Product per variant with every shared field repeated. Group them.

{
  "@context": "https://schema.org/",
  "@type": "ProductGroup",
  "name": "Example T-Shirt",
  "description": "Cotton t-shirt.",
  "url": "https://example.com/products/example-tshirt",
  "brand": { "@type": "Brand", "name": "ExampleBrand" },
  "productGroupID": "TSHIRT-001",
  "variesBy": ["https://schema.org/color", "https://schema.org/size"],
  "hasVariant": [
    {
      "@type": "Product",
      "sku": "TSHIRT-001-RED-M",
      "name": "Example T-Shirt, Red, M",
      "color": "Red",
      "size": "M",
      "inProductGroupWithID": "TSHIRT-001",
      "offers": {
        "@type": "Offer",
        "url": "https://example.com/products/example-tshirt?variant=red-m",
        "priceCurrency": "USD",
        "price": 19.99,
        "availability": "https://schema.org/InStock",
        "itemCondition": "https://schema.org/NewCondition"
      }
    }
  ]
}

The hasVariant array holds one Product per real variant; add more objects to it for each color and size permutation you sell. Google's product variant documentation (last updated 2026-05-20) documents ProductGroup with a productGroupID, a variesBy array naming the varying attributes (for example schema.org/color and schema.org/size), and a hasVariant array of Product objects, each carrying its varying attribute values, an inProductGroupWithID back-reference, and its own Offer; Google describes nesting variants under hasVariant as the most compact and natural representation.Spec-factGoogle, Product variant structured data

The pattern stays token-efficient because the shared attributes (name, description, brand, url, productGroupID) are stated once on the group, and each entry in hasVariant restates only what actually varies plus its own offer. A catalog with many size and color permutations therefore avoids repeating identical fields on every variant, which is both smaller to parse and easier to keep consistent.Hypothesis (our analysis)

Before you ship

  • Paste each skeleton into Google's Rich Results Test and into validator.schema.org, and resolve every error and warning before it goes near a product template.
  • Replace 9780201379624 with the product's real GTIN, and confirm the check digit is correct so validation passes.
  • Confirm availability and itemCondition use full schema.org URLs, not bare strings like InStock.
  • For variant products, check that every entry in hasVariant carries its own offers, its varying attributes, and an inProductGroupWithID that matches the group's productGroupID.
  • Diff the facts in the JSON-LD against the visible page. Any price, availability, or policy that appears in one and not the other is an equivalence problem, and the fix is to make them match.

These skeletons are the structured layer of a bigger picture. Pair this with Product schema for AI shopping for the full field walkthrough and the eligibility rules, and read information density on machine surfaces for why the fields an agent can match earn the token budget. When you also serve a Markdown copy of the page, keep the same facts in both by following the product Markdown mirror blueprint, and see the machine-readability score for how all of this rolls up into one measure of how usable your data is to an agent.

Version 1.1
  • 2026-07-08, v1.1: The validation guidance is complete; the skeletons remain unvalidated until you run them through the Rich Results Test and validator.schema.org.
  • 2026-07-08, v1.0: Initial publication.

[ newsletter ]