WooCommerce

Product Schema for WooCommerce: Price, Returns, Rich Results

What Google needs from a WooCommerce product page to show price, availability, ratings and returns in results: what WooCommerce emits, the gaps, and the fix.

5 min read
WooCommerceSEOStructured Data
949 words5 min read

Why product schema is worth the hour

A product result in Google with a price, "In stock", a star rating and "Free 30-day returns" underneath it takes more space and gets more clicks than a plain blue link. Google builds that from structured data on the product page. It also feeds Google Shopping's free listings and the Merchant Center's automatic product feed. For a store, this is the highest-return SEO markup there is, and WooCommerce gets it partly right by default.

What WooCommerce emits

On single product pages WooCommerce outputs a Product JSON-LD block with name, description, SKU, image, URL, and an Offer (or AggregateOffer for variable products) with price, currency and availability, plus AggregateRating and Review when reviews exist.

It is a reasonable baseline and it has gaps that Google's Rich Results Test will flag as warnings or, in some cases, errors that disqualify the page from rich results.

The fields that matter, and the gaps

Offer: price and validity

price and priceCurrency are present. priceValidUntil is not, and Google warns about it. For sale prices, WooCommerce emits the sale price but not the schedule. For stores where prices are stable, a priceValidUntil a year out is honest and clears the warning; for scheduled sales, use the sale end date.

Since 2023, Google prefers priceSpecification with UnitPriceSpecification for pricing, and supports strike-through pricing via a priceType of ListPrice alongside the sale price. WooCommerce does not emit this; adding it lets Google show the discount.

Availability

InStock, OutOfStock, PreOrder, BackOrder map to WooCommerce's stock status and are emitted correctly in recent versions. Check variable products: the parent's AggregateOffer should reflect whether any variation is in stock.

Shipping details

OfferShippingDetails tells Google the shipping cost and delivery time by region, and Google shows it in results. WooCommerce does not emit it. For stores with a flat rate or free shipping to their main market, a static block with shippingRate, shippingDestination and deliveryTime is straightforward and unlocks the shipping line in results.

Returns

MerchantReturnPolicy is the newest and most visible addition: Google shows "Free 30-day returns" in results when it is present. Fields: applicableCountry, returnPolicyCategory (for example MerchantReturnFiniteReturnWindow), merchantReturnDays, returnMethod, returnFees. WooCommerce does not emit it. It is the same for every product, so it is one block added site-wide.

Both shipping and returns can alternatively be configured in Merchant Center for the whole store; the schema is the way to do it without a Merchant Center account and to make the page itself eligible.

Identifiers

gtin, mpn, brand. Google uses these to match your product to its knowledge of the product and strongly prefers them for Shopping. WooCommerce Core added a GTIN/UPC/EAN/ISBN field in 9.2; ensure it is populated and emitted. Brand needs a source: a product attribute, a taxonomy or a plugin field, mapped into brand: { "@type": "Brand", "name": ... }.

Reviews and ratings

Emitted when WooCommerce reviews are used. Stores using a third-party review platform (Judge.me, Trustpilot, Yotpo) usually have the platform emit its own AggregateRating, and then there are two, which Google dislikes. One source; remove the other.

Image

Present, but ensure it is the full-size URL and that variable products include variation images where they differ.

Variable products

Google's guidance for variants is to either mark up each variant as its own Product with ProductGroup linking them, or use AggregateOffer with lowPrice, highPrice and offerCount. WooCommerce uses the latter, which is acceptable; the ProductGroup approach is richer and lets specific variants appear, but it is a project.

Where to add the missing fields

WooCommerce exposes the structured data array via the woocommerce_structured_data_product filter (and _offer, _review variants). A small plugin adds the missing pieces:

PHP
add_filter( 'woocommerce_structured_data_product', function ( $markup, $product ) {
  $markup['brand'] = [ '@type' => 'Brand', 'name' => get_brand_name( $product ) ];

  $returns = [
    '@type'                => 'MerchantReturnPolicy',
    'applicableCountry'    => 'GB',
    'returnPolicyCategory' => 'https://schema.org/MerchantReturnFiniteReturnWindow',
    'merchantReturnDays'   => 30,
    'returnMethod'         => 'https://schema.org/ReturnByMail',
    'returnFees'           => 'https://schema.org/FreeReturn',
  ];
  $shipping = [
    '@type'               => 'OfferShippingDetails',
    'shippingRate'        => [ '@type' => 'MonetaryAmount', 'value' => 0, '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' ],
    ],
  ];

  $offers = isset( $markup['offers'][0] ) ? $markup['offers'] : [ $markup['offers'] ];
  foreach ( $offers as &$offer ) {
    $offer['priceValidUntil']      = gmdate( 'Y-m-d', strtotime( '+1 year' ) );
    $offer['hasMerchantReturnPolicy'] = $returns;
    $offer['shippingDetails']      = $shipping;
  }
  $markup['offers'] = $offers;
  return $markup;
}, 10, 2 );

Adjust country, currency and policy to the store's actual terms. The values must be true; Google does check.

SEO plugins (Yoast WooCommerce SEO, Rank Math) add some of these fields with configuration and may replace WooCommerce's block entirely. Use one source of product schema, not two.

Validating

Rich Results Test on a simple product, a variable product, a sale product and an out-of-stock product. Zero errors; warnings only for fields you genuinely cannot supply. Then Search Console's Shopping and Product snippets reports after a couple of weeks for coverage and errors across the catalogue. Errors there for "missing field price" or "aggregateRating conflicts" point to specific products or the duplicate-source problem.

Where this fits

Product schema is one of the first things checked in a WooCommerce SEO review, because the default output is close, the gaps are consistent, and the fix is a small plugin that lifts every product page's result at once. Returns and shipping details in particular are recent enough that most competing stores do not have them yet.

All writingHire me for this