Merchant Listing Schema Framework

Use conditional-logic schema generation to keep Merchant Listing markup aligned with real page state and avoid stale or invalid static templates.

When To Use It

Google Search CentralMerchant Listing documentation

Use on product pages where a shopper can buy the product: only those pages are eligible for merchant listing experiences, and the required merchant listing properties also make the page eligible for product snippets. Merchant listings go further than product snippets: the price must be greater than zero, offers must be a single Offer, and shipping, returns, member prices and unit pricing can be described. Editorial review pages that don't sell the product belong in the Product snippet framework.

Key Implementation Documentation Highlights

  • Required properties: name, image, offers. offers must be an Offer (AggregateOffer is not supported for merchant listings) with price or priceSpecification.price, and priceCurrency or priceSpecification.priceCurrency.
  • Price rules: the active price must be greater than zero (product snippets accept 0, merchant listings don't). Use a JSON number or a string with a dot as the decimal separator, and a three-letter ISO 4217 currency code. If offers.price and priceSpecification both encode an active price, Google uses offers.price. Sell in several currencies from distinct URLs, one per currency.
  • Recommended Offer properties: availability (one ItemAvailability value such as InStock, OutOfStock or PreOrder), itemCondition (NewCondition, RefurbishedCondition or UsedCondition), a single url, priceValidUntil (a past date can stop the listing from showing), validFrom and validThrough for sale windows (start no later than end), shippingDetails and hasMerchantReturnPolicy.
  • Strikethrough and member prices go in priceSpecification as UnitPriceSpecification entries: priceType StrikethroughPrice (ListPrice is the former name) marks the original price and should be higher than the active price; validForMemberTier links a price to a loyalty tier by @id (the tier is defined under Organization hasMemberProgram), optionally with a whole number membershipPointsEarned. Never put priceType and validForMemberTier on the same entry; the active price has neither.
  • Unit pricing: referenceQuantity on a UnitPriceSpecification (value, unitCode such as ML, and valueReference for the base measure). It matters most in the EU, New Zealand and Australia for products sold by volume, length or weight.
  • Shipping: each OfferShippingDetails entry carries a shippingRate (MonetaryAmount with currency and value or maxValue; value 0 for free shipping), a shippingDestination DefinedRegion (ISO 3166-1 alpha-2 addressCountry; optional addressRegion as a 2 or 3 character ISO 3166-2 code without the country prefix, postalCode or postalCodeRange) and a deliveryTime (handlingTime and transitTime in days with unitCode DAY or d, cutoffTime with a UTC offset, businessDays). doesNotShip and shippingOrigin are also recommended.
  • Returns: a MerchantReturnPolicy needs applicableCountry (ISO 3166-1 alpha-2, up to 50) and returnPolicyCategory. MerchantReturnFiniteReturnWindow requires merchantReturnDays; returnFees ReturnShippingFees requires returnShippingFeesAmount. returnMethod, returnFees, returnPolicyCountry and merchantReturnLink are recommended. Google recommends a global return and shipping policy under Organization instead of repeating them per Offer: see the Organization framework.
  • Recommended product properties: description (strongly recommended), brand (one), gtin (or gtin8, gtin12, gtin13, gtin14), mpn, sku, color, size, material, pattern, audience (PeopleAudience), category (text or a Google Product Category CategoryCode), aggregateRating, review, hasCertification (up to 10), hasAdultConsideration, subjectOf (a glTF 3D model), plus inProductGroupWithID and isVariantOf for variants.
  • Identifier and value rules: gtin is numeric with 8, 12, 13 or 14 digits (the URL form is not supported); sku has no whitespace; availability, itemCondition, brand, size, inProductGroupWithID, subjectOf and offers.url take one value each; hasAdultConsideration only accepts SexualContentConsideration.
  • Images must be crawlable, indexable and represent the product, in a format Google Images supports; Google recommends at least 50K pixels (width x height) and multiple high resolution images.
  • Mark up pages that focus on a single product (or variants of one product), not category or listing pages. Prefer markup in the initial HTML: JavaScript-generated markup can make Shopping crawls less frequent and less reliable for fast-changing price and availability. Pages promoting widely prohibited or regulated goods are not allowed.
  • Variants of one product use a ProductGroup with hasVariant, where each variant Offer follows these rules: see the Product Variants framework.
  • Validate with the Rich Results Test and URL Inspection, then monitor the related Search Console report for errors and warnings after deployment.

Template Approach

const pageData = {
  name: "Ridgeline 3L Waterproof Rain Jacket, Women's",
  image: "https://www.example.com/img/ridgeline-womens-1x1.jpg",
  price: "179.00",
  currency: "USD",
  sku: "TH-RIDGE-W-SPR-M"
};

const staticTemplate = {
  "@context": "https://schema.org",
  "@type": "Product",
  "name": pageData.name,
  "image": [pageData.image],
  "sku": pageData.sku,
  "offers": {
    "@type": "Offer",
    "price": pageData.price,
    "priceCurrency": pageData.currency,
    "availability": "https://schema.org/InStock"
  }
};

Conditional-logic Framework

const ISO_DATE_TIME = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2})(?::(\d{2}))?(Z|[+-]\d{2}:\d{2})?)?$/;

function isAbsoluteUrl(value) {
  return typeof value === "string" && /^https?:\/\/[^\s/?#]+\.[^\s/?#]+(?:[/?#]\S*)?$/i.test(value);
}

// Returns the ISO 8601 string unchanged (timezone offset kept), or null if unparseable.
function toIsoDateTime(value) {
  if (typeof value !== "string") return null;
  const text = value.trim();
  const m = text.match(ISO_DATE_TIME);
  if (!m) return null;
  const [, y, mo, d, h = "00", mi = "00", s = "00"] = m;
  const date = new Date(Date.UTC(+y, +mo - 1, +d));
  if (date.getUTCMonth() !== +mo - 1 || date.getUTCDate() !== +d) return null;
  if (+h > 23 || +mi > 59 || +s > 59) return null;
  return text;
}

function toIsoDate(value) {
  const iso = toIsoDateTime(value);
  return iso ? iso.slice(0, 10) : null;
}

// Drops null, undefined, "", [] and {} recursively.
function compact(value) {
  if (Array.isArray(value)) {
    const items = value.map(compact).filter((v) => v !== undefined);
    return items.length ? items : undefined;
  }
  if (value && typeof value === "object") {
    const out = {};
    for (const [key, v] of Object.entries(value)) {
      const c = compact(v);
      if (c !== undefined) out[key] = c;
    }
    return Object.keys(out).length ? out : undefined;
  }
  return value === null || value === undefined || value === "" ? undefined : value;
}

function passesPageGates(source) {
  if (source.indexable === false) return false;
  if (source.canonicalUrl && source.url && source.canonicalUrl !== source.url) return false;
  return source.contentVisible !== false;
}


const S = "https://schema.org/";
const AVAILABILITY = ["BackOrder", "Discontinued", "InStock", "InStoreOnly", "LimitedAvailability", "OnlineOnly", "OutOfStock", "PreOrder", "PreSale", "SoldOut"];
const CONDITIONS = ["NewCondition", "RefurbishedCondition", "UsedCondition"];
const RETURN_CATEGORY = { finite: "MerchantReturnFiniteReturnWindow", not_permitted: "MerchantReturnNotPermitted", unlimited: "MerchantReturnUnlimitedWindow" };
const RETURN_FEES = { free: "FreeReturn", customer_pays: "ReturnFeesCustomerResponsibility", shipping_fee: "ReturnShippingFees" };
const RETURN_METHOD = { kiosk: "ReturnAtKiosk", mail: "ReturnByMail", in_store: "ReturnInStore" };
const GENDERS = ["male", "female", "unisex"];
const WEEKDAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];

function toNumber(value) {
  if (value === null || value === undefined || value === "" || typeof value === "boolean") return null;
  const num = Number(value);
  return Number.isFinite(num) ? num : null;
}

const isCurrencyCode = (value) => typeof value === "string" && /^[A-Z]{3}$/.test(value);
const isCountryCode = (value) => typeof value === "string" && /^[A-Z]{2}$/.test(value);
const isRegionCode = (value) => typeof value === "string" && /^[A-Z0-9]{1,3}$/.test(value);
const isGtin = (value) => /^(\d{8}|\d{12,14})$/.test(String(value ?? "")); // numeric only, never a URL
const isSku = (value) => typeof value === "string" && /^\S+$/.test(value);   // no whitespace
const isIsoTime = (value) => typeof value === "string" && /^([01]\d|2[0-3]):[0-5]\d(:[0-5]\d)?(Z|[+-]\d{2}:\d{2})$/.test(value);
const schemaEnum = (value, allowed) => (allowed.includes(value) ? S + value : null);
// Comparable instant in ms; date-only and offset-less values are read as UTC.
const toInstant = (iso) =>
  Date.parse(/(Z|[+-]\d{2}:\d{2})$/.test(iso) ? iso : iso.length === 10 ? `${iso}T00:00:00Z` : `${iso}Z`);
const wholeNumber = (value) => (Number.isInteger(value) && value >= 0 ? value : null);

// Rating or AggregateRating; null when ratingValue falls outside worstRating..bestRating.
function buildRating(input, type) {
  if (!input) return null;
  const best = toNumber(input.bestRating) ?? 5;
  const worst = toNumber(input.worstRating) ?? 1;
  const value = toNumber(input.ratingValue);
  if (value === null || worst >= best || value < worst || value > best) return null;
  const rating = { "@type": type, ratingValue: value, bestRating: best, worstRating: worst };
  if (type === "AggregateRating") {
    const ratingCount = toNumber(input.ratingCount);
    const reviewCount = toNumber(input.reviewCount);
    if (!(ratingCount > 0) && !(reviewCount > 0)) return null;
    rating.ratingCount = ratingCount > 0 ? ratingCount : null;
    rating.reviewCount = reviewCount > 0 ? reviewCount : null;
  }
  return rating;
}

// Review with a valid author name (under 100 characters) and an in-range rating.
function buildReview(input) {
  const authorName = String(input?.authorName ?? "").trim();
  const reviewRating = buildRating(input?.rating, "Rating");
  if (!authorName || authorName.length >= 100 || !reviewRating) return null;
  return {
    "@type": "Review",
    author: { "@type": input.authorType === "Organization" ? "Organization" : "Person", name: authorName },
    reviewRating,
    datePublished: toIsoDate(input.datePublished)
  };
}

function buildRegion(country, regions = [], postalCodes = []) {
  if (!isCountryCode(country)) return null;
  return {
    "@type": "DefinedRegion",
    addressCountry: country,
    addressRegion: regions.filter(isRegionCode),       // ISO 3166-2 subdivision without the country prefix
    postalCode: postalCodes.filter((p) => typeof p === "string" && p.trim())
  };
}

const daysRange = ([min, max] = []) =>
  wholeNumber(min) !== null && wholeNumber(max) !== null && min <= max
    ? { "@type": "QuantitativeValue", minValue: min, maxValue: max, unitCode: "DAY" }
    : null;

// One OfferShippingDetails per destination: shippingRate, shippingDestination and deliveryTime are required in it.
function buildShipping(s, origin) {
  const destination = buildRegion(s?.country, s?.regions, s?.postalCodes);
  const rate = toNumber(s?.rate);
  const handlingTime = daysRange(s?.handlingDays);
  const transitTime = daysRange(s?.transitDays);
  if (!destination || rate === null || rate < 0 || !isCurrencyCode(s.currency)) return null; // value 0 means free shipping
  if (!handlingTime && !transitTime) return null;
  const days = (s.businessDays ?? []).filter((d) => WEEKDAYS.includes(d));
  return {
    "@type": "OfferShippingDetails",
    shippingRate: { "@type": "MonetaryAmount", value: rate, currency: s.currency },
    shippingDestination: destination,
    shippingOrigin: buildRegion(origin?.country, origin?.regions),
    deliveryTime: {
      "@type": "ShippingDeliveryTime",
      handlingTime,
      transitTime,
      cutoffTime: isIsoTime(s.cutoffTime) ? s.cutoffTime : null,
      businessDays: days.length ? { "@type": "OpeningHoursSpecification", dayOfWeek: days.map((d) => S + d) } : null
    }
  };
}

// Finite windows need merchantReturnDays; ReturnShippingFees needs returnShippingFeesAmount.
function buildReturnPolicy(r) {
  const countries = (r?.countries ?? []).filter(isCountryCode).slice(0, 50);
  const category = RETURN_CATEGORY[r?.category];
  if (!category || countries.length === 0) return null;
  const finite = category === "MerchantReturnFiniteReturnWindow";
  const days = wholeNumber(r.days);
  if (finite && !(days > 0)) return null;
  const fee = toNumber(r.feeAmount);
  const chargesShipping = r.fees === "shipping_fee";
  if (chargesShipping && !(fee !== null && fee >= 0 && isCurrencyCode(r.feeCurrency))) return null;
  return {
    "@type": "MerchantReturnPolicy",
    applicableCountry: countries,
    returnPolicyCountry: (r.returnToCountries ?? []).filter(isCountryCode),
    returnPolicyCategory: S + category,
    merchantReturnDays: finite ? days : null,
    returnMethod: (r.methods ?? []).map((m) => RETURN_METHOD[m]).filter(Boolean).map((m) => S + m),
    returnFees: RETURN_FEES[r.fees] ? S + RETURN_FEES[r.fees] : null,
    returnShippingFeesAmount: chargesShipping ? { "@type": "MonetaryAmount", value: fee, currency: r.feeCurrency } : null,
    merchantReturnLink: isAbsoluteUrl(r.link) ? r.link : null
  };
}

// Extra UnitPriceSpecification entries: strikethrough, member tier and unit pricing.
// An active price has neither priceType nor validForMemberTier; never combine the two.
function buildPriceSpecifications(offer, price, currency) {
  const specs = [];
  const was = toNumber(offer.strikethroughPrice);
  if (was !== null && was > price) {
    specs.push({ "@type": "UnitPriceSpecification", priceType: S + "StrikethroughPrice", price: was, priceCurrency: currency });
  }
  const member = offer.memberPrice;
  const memberPrice = toNumber(member?.price);
  if (memberPrice > 0 && isAbsoluteUrl(member.tierId)) {
    specs.push({
      "@type": "UnitPriceSpecification",
      price: memberPrice,
      priceCurrency: currency,
      validForMemberTier: { "@id": member.tierId },             // tier defined under Organization.hasMemberProgram
      membershipPointsEarned: wholeNumber(member.pointsEarned)
    });
  }
  const unit = offer.unitPricing;
  const quantity = toNumber(unit?.value);
  const base = toNumber(unit?.baseValue);
  if (quantity > 0 && /^[A-Z0-9]{2,3}$/.test(unit.unitCode ?? "")) {
    specs.push({
      "@type": "UnitPriceSpecification",
      price,
      priceCurrency: currency,
      referenceQuantity: {
        "@type": "QuantitativeValue",
        value: quantity,
        unitCode: unit.unitCode,                                 // for example ML
        valueReference: base > 0 ? { "@type": "QuantitativeValue", value: base, unitCode: unit.baseUnitCode ?? unit.unitCode } : null
      }
    });
  }
  return specs;
}

// A single Offer (AggregateOffer is not supported) with price > 0 and an ISO 4217 currency.
function buildOffer(offer, source, now) {
  if (!offer || offer.lowPrice !== undefined) return null;
  const price = toNumber(offer.price);
  const currency = offer.currency;
  if (!(price > 0) || !isCurrencyCode(currency)) return null;

  // Sale window: validFrom must not be later than validThrough or priceValidUntil.
  const validUntil = toIsoDate(offer.priceValidUntil);
  const priceValidUntil = validUntil && toInstant(`${validUntil}T23:59:59`) >= now.getTime() ? validUntil : null; // a past date can hide the listing
  let validFrom = toIsoDateTime(offer.validFrom);
  let validThrough = toIsoDateTime(offer.validThrough);
  const end = validThrough ? toInstant(validThrough) : priceValidUntil ? toInstant(`${priceValidUntil}T23:59:59`) : null;
  if (validFrom && end !== null && toInstant(validFrom) > end) validFrom = validThrough = null;

  return {
    "@type": "Offer",
    url: isAbsoluteUrl(offer.url) ? offer.url : null,
    price,
    priceCurrency: currency,
    priceSpecification: buildPriceSpecifications(offer, price, currency),
    priceValidUntil,
    validFrom,
    validThrough,
    availability: schemaEnum(offer.availability, AVAILABILITY),
    itemCondition: schemaEnum(offer.condition, CONDITIONS),
    shippingDetails: (source.shipping ?? []).map((s) => buildShipping(s, source.shipsFrom)),
    hasMerchantReturnPolicy: buildReturnPolicy(source.returns)
  };
}

function buildAudience(a) {
  if (!a) return null;
  const min = toNumber(a.minAge);
  const max = toNumber(a.maxAge);
  return {
    "@type": "PeopleAudience",
    suggestedGender: GENDERS.includes(a.gender) ? a.gender : null,
    suggestedMinAge: min !== null && min >= 0 ? min : null,
    suggestedMaxAge: max !== null && max >= 0 && (min === null || max >= min) ? max : null
  };
}

function buildMerchantListingSchema(source, now = new Date()) {
  if (!passesPageGates(source)) return null;
  if (source.purchasable !== true) return null;             // no checkout on this page: use the Product snippet framework

  const name = String(source.name ?? "").trim();
  const images = (source.images ?? []).filter(isAbsoluteUrl);
  const offers = buildOffer(source.offer, source, now);
  if (!name || images.length === 0 || !offers) return null; // name, image and offers are required

  return compact({
    "@context": "https://schema.org",
    "@type": "Product",
    name,
    image: images,
    description: source.description,
    sku: isSku(source.sku) ? source.sku : null,
    mpn: source.mpn,
    gtin: isGtin(source.gtin) ? String(source.gtin) : null,
    brand: source.brand ? { "@type": "Brand", name: source.brand } : null,
    category: source.category,
    color: source.color,
    material: source.material,
    pattern: source.pattern,
    size: source.size,
    audience: buildAudience(source.audience),
    aggregateRating: buildRating(source.rating, "AggregateRating"),
    review: (source.reviews ?? []).map(buildReview).filter(Boolean),
    offers
  });
}

// Usage:
// const source = {
//   url: "https://www.example.com/shop/ridgeline-rain-jacket-womens",
//   canonicalUrl: "https://www.example.com/shop/ridgeline-rain-jacket-womens",
//   indexable: true,
//   contentVisible: true,
//   purchasable: true,
//   name: "Ridgeline 3L Waterproof Rain Jacket, Women's",
//   description: "Three-layer waterproof shell with pit zips, a helmet-compatible hood and taped seams.",
//   images: [
//     "https://www.example.com/img/ridgeline-womens-1x1.jpg",
//     "https://www.example.com/img/ridgeline-womens-4x3.jpg",
//     "https://www.example.com/img/ridgeline-womens-16x9.jpg"
//   ],
//   brand: "Trailhead Outfitters",
//   sku: "TH-RIDGE-W-SPR-M",
//   mpn: "RDG3L-W-26",
//   gtin: "00812345670142",
//   category: "Apparel & Accessories > Clothing > Outerwear > Coats & Jackets",
//   color: "Spruce",
//   material: "Recycled nylon",
//   pattern: "Solid",
//   size: "M",
//   audience: { gender: "female", minAge: 13 },
//   rating: { ratingValue: 4.6, reviewCount: 128 },
//   reviews: [{ authorName: "Priya Shah", rating: { ratingValue: 5 }, datePublished: "2026-09-14" }],
//   offer: {
//     url: "https://www.example.com/shop/ridgeline-rain-jacket-womens",
//     price: 179,
//     currency: "USD",
//     strikethroughPrice: 229,
//     memberPrice: { price: 161, tierId: "https://www.example.com/rewards#tier-gold", pointsEarned: 179 },
//     validFrom: "2026-10-01T00:00:00-04:00",
//     validThrough: "2026-10-31T23:59:59-04:00",
//     priceValidUntil: "2026-10-31",
//     availability: "InStock",
//     condition: "NewCondition"
//   },
//   shipsFrom: { country: "US", regions: ["MA"] },
//   shipping: [
//     {
//       country: "US",
//       rate: 0,
//       currency: "USD",
//       handlingDays: [0, 1],
//       transitDays: [2, 5],
//       cutoffTime: "14:00:00-04:00",
//       businessDays: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
//     }
//   ],
//   returns: {
//     countries: ["US"],
//     returnToCountries: ["US"],
//     category: "finite",
//     days: 30,
//     methods: ["mail", "in_store"],
//     fees: "shipping_fee",
//     feeAmount: 6.95,
//     feeCurrency: "USD",
//     link: "https://www.example.com/help/returns"
//   }
// };
// const jsonLd = buildMerchantListingSchema(source);

Why Conditional Logic Is Better Than Static Templates

  • Refuses to emit merchant listing markup when the page can't take an order, the price is zero or the currency is not ISO 4217, instead of shipping invalid offers.
  • Builds strikethrough, member and unit prices only when the data supports them, so a stale sale price or a member price without a tier never leaks into markup.
  • Drops a past priceValidUntil and a sale window whose start is after its end, which would otherwise hide the listing or contradict the visible price.
  • Keeps shipping and return policies consistent with their conditional rules (finite windows need days, return shipping fees need an amount) from one code path.

Property Reference

What Google's Merchant listing documentation requires and recommends, property by property. These are the same rules the SchemaCDN validator checks. Each name links to its schema.org definition.

Required, one of: Include offers.price or offers.priceSpecification.price.

Required, one of: Include offers.priceCurrency or offers.priceSpecification.priceCurrency.

Required, one of: In aggregateRating: at least one of ratingCount or reviewCount is required.

Merchant listing properties in Google's documentation
PropertyGoogleExpectsNotes
nameRequiredTextThe name of the product.
imageRequiredURL or ImageObject, can repeatThe URL of a product photo. Prefer multiple high resolution images. Aspect ratios: 16x9, 4x3, 1x1.
offersRequiredOffer, can repeatA nested Offer to sell the product. AggregateOffer is not supported for merchant listings.
offers.priceOne ofNumber or TextThe current, active offer price. Must be greater than zero. Or use priceSpecification.price.
offers.priceCurrencyOne ofTextIn three-letter ISO 4217 format. Or use priceSpecification.priceCurrency.
offers.priceSpecificationOne ofUnitPriceSpecification, can repeatFor complex pricing (strikethrough, member, unit prices).
offers.availabilityRecommendedEnumSpecify only one ItemAvailability value. One of: BackOrder, Discontinued, InStock, InStoreOnly, LimitedAvailability, OnlineOnly, and 4 more.
offers.itemConditionRecommendedEnumSpecify only one value. One of: NewCondition, RefurbishedCondition, UsedCondition.
offers.hasMerchantReturnPolicyRecommendedMerchantReturnPolicy, can repeatOr, preferably, a global return policy under Organization.
offers.shippingDetailsRecommendedOfferShippingDetails, can repeatOr, preferably, a shipping policy under Organization.
offers.priceValidUntilRecommendedDateISO 8601. Your listing may not display if it indicates a past date.
offers.urlRecommendedURLThe product page URL where the buyer can purchase. Can be omitted; don't provide multiple URLs.
offers.validFromRecommendedDateTime or DateISO 8601 start of a sale price.
offers.validThroughRecommendedDateTime or DateISO 8601 end of a sale price.
aggregateRatingRecommendedAggregateRatingFollow the Review snippet guidelines.
aggregateRating.ratingValueRequiredNumber or TextThe average rating for the item.
aggregateRating.ratingCountOne ofNumberThe total number of ratings for the item on your site.
aggregateRating.reviewCountOne ofNumberThe number of people who provided a review with or without an accompanying rating.
aggregateRating.bestRatingRecommendedNumberThe highest value allowed in this rating system. If omitted, 5 is assumed.
aggregateRating.worstRatingRecommendedNumberThe lowest value allowed in this rating system. If omitted, 1 is assumed.
audienceRecommendedPeopleAudienceThe suggested gender and age group. Only PeopleAudience is supported.
audience.suggestedGenderRecommendedText or EnumMale, female or unisex. One of: male, female, unisex, Male, Female.
audience.suggestedMinAgeRecommendedNumberIn years.
audience.suggestedMaxAgeRecommendedNumberIn years.
brandRecommendedBrand or OrganizationThe brand of the product. Specify at most one.
brand.nameRecommendedTextMaximum one.
categoryRecommendedText or CategoryCode, can repeatA custom category or Google Product Category (GPC) as CategoryCode.
colorRecommendedTextFor example red or yellow/sky blue.
descriptionRecommendedTextStrongly recommended.
gtinRecommendedText. Numeric form only; URL form is not supported. Or use the most specific gtin8, gtin12, gtin13, gtin14.
gtin8RecommendedTextUse if applicable.
gtin12RecommendedTextUse if applicable.
gtin13RecommendedTextUse if applicable.
gtin14RecommendedTextUse if applicable.
isbnRecommendedTextOnly valid on Book co-typed with Product. ISBN-13 recommended.
hasAdultConsiderationRecommendedEnumFor adult products. One of: SexualContentConsideration.
hasCertificationRecommendedCertification, can repeatEnergy efficiency or other certifications.
hasCertification.nameRequiredTextThe certification type, for example EPREL (EU energy label) or Vehicle_CO2_Class.
hasCertification.issuedByRequiredOrganizationThe organization that issued the certification.
hasCertification.certificationIdentificationRecommendedTextThe certification identifier, for example the EPREL registration number.
hasCertification.certificationRatingRecommendedRatingWith ratingValue, for example the energy or CO2 class (D).
hasEnergyConsumptionDetailsRecommendedEnergyConsumptionDetailsDeprecated: use hasCertification for EU energy labels instead.
hasEnergyConsumptionDetails.hasEnergyEfficiencyCategoryRequiredEnumThe product's energy efficiency class. One of: EUEnergyEfficiencyCategoryA3Plus, EUEnergyEfficiencyCategoryA2Plus, EUEnergyEfficiencyCategoryA1Plus, EUEnergyEfficiencyCategoryA, EUEnergyEfficiencyCategoryB,...
hasEnergyConsumptionDetails.energyEfficiencyScaleMinRecommendedEnumLeast efficient class on the label scale.
hasEnergyConsumptionDetails.energyEfficiencyScaleMaxRecommendedEnumMost efficient class on the label scale.
inProductGroupWithIDRecommendedTextThe product group ID this variant belongs to. Maximum one.
isVariantOfRecommendedProductGroupThe ProductGroup this product is a variant of.
materialRecommendedTextFor example Leather or Cotton/Polyester.
mpnRecommendedTextThe manufacturer part number.
patternRecommendedTextFor example polka dots.
reviewRecommendedReview, can repeatThe reviewer's name must be a valid Person or Team.
review.authorRequiredPerson or OrganizationThe author of the review. The reviewer's name must be a valid name. Up to 100 characters.
review.reviewRatingRequiredRatingThe rating given in this review.
review.datePublishedRecommendedDate or DateTimeThe date the review was published, in ISO 8601 format.
sizeRecommendedText or SizeSpecificationFor example XL or medium. Specify at most one value.
size.nameRecommendedTextA human readable size value, for example XL or medium.
size.sizeGroupRecommendedEnum or Text, can repeatThe suggested size group for the product. One of: WearableSizeGroupBig, WearableSizeGroupMaternity, WearableSizeGroupPetite, WearableSizeGroupPlus, WearableSizeGroupRegular, WearableSizeGroupTall.
size.sizeSystemRecommendedEnum or TextThe size system used. One of: WearableSizeSystemAU, WearableSizeSystemBR, WearableSizeSystemCN, WearableSizeSystemDE, WearableSizeSystemEurope, WearableSizeSystemFR, and 5 more.
skuRecommendedTextMerchant-specific identifier. Unicode characters valid for interchange, no whitespace.
subjectOfRecommended3DModelA 3D model of the product in glTF format. Maximum one.
subjectOf.encodingRequiredMediaObjectWith contentUrl to a .gltf or .glb file.

Explore Other Schema Framework Pages

Limited Movie Use for movie detail pages with canonical film information and transparent metadata attribution. Movie Open Organization Use for official organization pages to clarify brand identity and authoritative entity signals. Organization Open Retired Practice Problems Use for educational practice content that presents solvable problems and answer validation structure. Quiz Open Product Snippet Use for product pages where pricing, availability, and product identity are visible to users. Product Open Product Variants Use when one product comes in variants such as size or color, grouped with ProductGroup. ProductGroup Open Profile Page Use for creator or contributor profile pages where identity attributes and ownership are explicit. ProfilePage Open Q&A Use for pages where users submit questions and community or editorial answers are displayed. QAPage Open Recipe Use for recipe pages with complete ingredients, instructions, and preparation details. Recipe Open Return Policy Use to publish a merchant return policy once at the organization level, with offer-level overrides. MerchantReturnPolicy Open Review Snippet Use when a page publishes genuine reviews tied to a clearly identified product, service, or entity. Review Open Shipping Policy Use to publish shipping rates, delivery times and destinations once at the organization level. ShippingService Open Site Name Use on the home page so Google shows the preferred site name in search results. WebSite Open Retired Sitelinks Search Box Use on sites with a functioning internal search endpoint that can accept URL query parameters. WebSite Open Software App (Beta) Use for software product pages that describe platform support and application category clearly. SoftwareApplication Open Limited Speakable Use for news-style pages where specific text segments are appropriate for text-to-speech playback. SpeakableSpecification Open Subscription and Paywalled Content Use for pages with restricted sections so Google can distinguish free previews from protected content. CreativeWork Open Limited Vacation Rental Use for rental property detail pages where booking-relevant amenities and location signals are explicit. VacationRental Open Retired Vehicle Listing Use for inventory pages selling vehicles with standardized listing fields and offer information. Car Open Video Use for pages where a primary video is embedded and key media metadata is visible on-page. VideoObject Open Article Use for editorial pages where the visible content is a complete article with a clear byline and publication timestamps. Article Open Limited Book Actions Use when your page helps users read, preview, or purchase books through clearly described actions. Book Open Breadcrumb Use on pages that belong to a clear hierarchy and display navigational trail links users can follow. BreadcrumbList Open Carousel Use when a page lists multiple related entities in a scannable sequence that can be surfaced as cards. ItemList Open Course Use for educational offerings where curriculum details, provider identity, and delivery context are explicit. Course Open Dataset Use for dataset landing pages so datasets are discoverable in Google Dataset Search with license, creator and download details. Dataset Open Discussion Forum Use for forum and community threads where a primary post and subsequent replies are publicly visible. DiscussionForumPosting Open Limited Education Q&A Use for educational question-answer pages where a single canonical answer and supporting context are visible. Quiz Open Employer Aggregate Rating Use on pages that publish employer rating aggregates from a clear and policy-compliant review source. EmployerAggregateRating Open Retired Estimated Salary Use for compensation insight pages that present transparent salary ranges for specific occupations and locations. Occupation Open Event Use for upcoming events with a real-world or virtual occurrence, schedule, and location context. Event Open Retired Fact Check Use for fact-checking pages that evaluate a specific claim with a transparent rating methodology. ClaimReview Open Retired Home Activities Use for virtual classes and activities where participation details and timing are clearly listed. Event Open Retired How-To Use for instructional content that teaches a process through ordered steps and actionable detail. HowTo Open Image Metadata Use on image pages and image hosts that expose ownership and licensing information to search engines. ImageObject Open Job Posting Use for active job listings that include role details, hiring entity, and location/salary context. JobPosting Open Retired Learning Video Use for educational videos where the learning objective and pedagogical structure are explicit. LearningResource Open Local Business Use for physical businesses serving local customers with publicly verifiable contact and location details. LocalBusiness Open Loyalty Program Use to publish a loyalty program and its tiers once at the organization level, so member prices and member shipping can reference them. MemberProgram Open Math Solvers Use for pages that solve mathematical expressions and present step-by-step computational guidance. MathSolver Open

Implement Merchant Listing Schema Frameworks Correctly

Get implementation support for conditional schema architecture, validation, and deployment governance.