# WooCommerce product schema: price, availability and reviews

> WooCommerce writes Product structured data for you, then a missing price, an unset stock status or a second plugin quietly breaks it. Find which field is gone and fix it in the product editor.

Updated 2026-09-25 · WordPress & WooCommerce · HTML version: https://getreport.app/guides/woocommerce-product-schema-price-availability-reviews

WooCommerce prints Product structured data on every product page without being asked, and on a healthy store that is enough for Google to show the price, "In stock" and the stars under the listing. When the report says a product is missing one of those fields, the cause is almost never the markup and almost always the product record, a hidden-price setting or a second plugin that took the block over. This guide shows what WooCommerce outputs, where each field comes from, and how to get all three back in about ten minutes per store.

## Quick answer

- WooCommerce builds the Product block from the product itself: `offers.price` from the regular price, `offers.availability` from the stock status, `aggregateRating` from approved reviews with a star rating.
- No price on the product (or "call for price") means no `offers` at all. Fill in the regular price; variations each need one.
- No stock status means no `availability`. Set it under Product data → Inventory, or manage stock with a quantity.
- No rating until real reviews exist and reviews are enabled under WooCommerce → Settings → Products. Do not add a made-up one.
- One Product per page. Yoast WooCommerce SEO and Rank Math's WooCommerce module extend WooCommerce's block; a builder widget or a second schema plugin duplicates it. Keep one source.
- Run the [WooCommerce checker](https://getreport.app/tools/woocommerce-checker) on a product page, fix what it lists, and confirm in Google's Rich Results Test.

## Why product schema matters

A product result with "€49.90 · In stock · ★ 4.7 (32)" under it answers the two questions a shopper has before clicking, and it takes more space than the plain blue link next to it. Google shows that only when the Product entity on the page carries the price, the availability and a rating, in the format it expects. The general rules are in [Product schema: price, availability and reviews](https://getreport.app/guides/product-schema-price-availability-reviews); this guide is the WooCommerce-specific part.

The same markup feeds Google Merchant Center. Free product listings in the Shopping tab need a price and an availability that match the page, and Merchant Center uses the schema.org Product markup on your product pages for automatic item updates, so a product whose price is missing from the schema is a product whose Shopping listing can be disapproved or shown with a stale price.

WooCommerce gets this right by default. Stores lose the fields later: a product imported without a stock status, a variable product with no variation prices, a B2B plugin that hides prices from guests, or a builder template that replaces the part of the page where WooCommerce prints its markup. Google then sees a valid Product with nothing to show, and the rich result disappears without a warning.

## How getReport checks it

> **Free tool:** [WooCommerce store checker](https://getreport.app/tools/woocommerce-checker): The WooCommerce-specific problems in one run: cart fragments slowing every page, a cart or checkout served from a page cache, Product schema without price, availability or rating, out-of-stock products left indexable, plus the plugin cost and duplicate cache plugins from the WordPress Doctor.

The checker recognises WooCommerce from the page itself (the `woocommerce` body class, a `wc-ajax` call, the plugin's assets or a Product JSON-LD block), then reads every Product entity in the page's JSON-LD. For each one it looks for `offers.price` (or `offers.lowPrice`, which is what variable products get), `offers.availability`, and either `aggregateRating.ratingValue` or a `review`. The finding lists each product by name with the field it lacks:

> **Check: WooCommerce Product schema has price, availability and rating.** Google shows the price, stock state and stars in results only when the Product block carries offers.price, offers.availability and a rating. WooCommerce outputs them when the product has a price, stock management and reviews; a theme, builder or SEO plugin replacing the block often drops them.
>
> 1. Fill in the product's regular price and stock status; enable reviews if you have them. If an SEO plugin or builder outputs its own Product block, let WooCommerce's (or the plugin's, not both) be the single source.
> 2. Check the result in Google's Rich Results Test after saving.

![The WooCommerce product schema finding opened on a product page: the title counts the missing rich-result fields, the detail names the product and the missing offers.availability, and the fix points to the stock status field](https://getreport.app/guides/img/woocommerce-product-schema-price-availability-reviews/product-schema.webp "The detail line names the product and the exact field, so you know which tab of the product editor to open.")

The rating line reads "aggregateRating or review (only if the product has reviews)". A product that has no reviews yet cannot have a rating, and the finding says so rather than asking you to invent one. Treat that line as informational until the first reviews arrive.

Two schema checks that run on every site sit next to it. The first lists the properties Google's merchant listing needs (`name`, `image`, `offers`, a price and `priceCurrency`); the second follows from it:

> **Check: Required schema properties.** Google shows a rich result (price, stars, breadcrumb, event date) only when the entity carries the properties it requires. Without them the block is valid but earns nothing in search.
>
> 1. Open the technical detail for the full list, one line per entity type and property.
> 2. Add each property to the JSON-LD block. In WooCommerce, Yoast or Rank Math this usually means filling in the price, image or author field on the item itself.

> **Check: Rich result eligibility.** Rich results (price, stars, breadcrumbs or event dates under your listing) make it bigger and earn more clicks than a plain blue link. An entity with missing required properties does not qualify.
>
> 1. Fix the properties listed under "required properties missing" first; eligibility follows automatically.
> 2. Test the page with Google's Rich Results Test after the change.

And when the page is WordPress, the report looks at which plugins are printing JSON-LD. It reads the `class` attribute WordPress SEO plugins put on their script tag (`yoast-schema-graph`, `rank-math-schema`, `aioseo-schema` and so on) and warns when two of them are active at once:

> **Check: One plugin generates the structured data.** Two plugins each printing Organization, WebSite or Product schema give Google conflicting entities. Rich results are then withheld rather than doubled.
>
> 1. Keep schema output in one plugin: disable schema in the SEO plugin or in the dedicated schema plugin, not both.
> 2. In WooCommerce shops, let WooCommerce print Product schema and let the SEO plugin extend it rather than duplicate it.

WooCommerce's own block does not count as a generator for that check, so WooCommerce plus one SEO plugin is the normal, passing case. Two Product blocks with the same name and URL are caught by the separate duplicate-entities check, which lists the product as appearing twice.

## Step by step

### 1. See what WooCommerce prints on a healthy product

Open a product page as a logged-out visitor, view the source and search for `"@type":"Product"`. WooCommerce outputs one block near the end of the page, generated from the product record when the single-product summary renders. On a simple product with reviews it looks like this (shortened):

```json
{
  "@context": "https://schema.org/",
  "@type": "Product",
  "@id": "https://example-shop.hr/product/linen-shirt/#product",
  "name": "Linen shirt",
  "url": "https://example-shop.hr/product/linen-shirt/",
  "description": "Washed linen, relaxed fit.",
  "image": "https://example-shop.hr/wp-content/uploads/linen-shirt.jpg",
  "sku": "LS-001",
  "offers": [{
    "@type": "Offer",
    "price": "49.90",
    "priceCurrency": "EUR",
    "availability": "http://schema.org/InStock",
    "url": "https://example-shop.hr/product/linen-shirt/",
    "seller": { "@type": "Organization", "name": "Example Shop", "url": "https://example-shop.hr" }
  }],
  "aggregateRating": { "@type": "AggregateRating", "ratingValue": "4.7", "reviewCount": "32" },
  "review": [{ "@type": "Review", "reviewRating": { "@type": "Rating", "ratingValue": "5" }, "author": { "@type": "Person", "name": "Ana" }, "reviewBody": "Fits well." }]
}
```

Every field maps to something in the product editor:

| Schema field | Comes from | Where to fix it |
| --- | --- | --- |
| `name`, `description`, `url` | Product title, short description, permalink | Product editor |
| `image` | Featured image | Product editor → Product image |
| `sku` | SKU field | Product data → Inventory |
| `offers.price`, `priceCurrency` | Regular/sale price, store currency | Product data → General; WooCommerce → Settings → General |
| `offers.availability` | Stock status or managed stock quantity | Product data → Inventory |
| `aggregateRating`, `review` | Approved reviews with star ratings | WooCommerce → Settings → Products → Reviews; the product's Reviews tab |

If your page has no Product block at all, skip to step 5: something removed the output.

### 2. Put the price back

WooCommerce only adds `offers` when the product has a price. A product saved with an empty price (common after a CSV import, or on "request a quote" products) gets a Product block with no offer, and the report then lists `offers` under required properties and both price and availability under the WooCommerce finding.

Open the product, Product data → General, and fill **Regular price**. A sale price on its own is not enough. For variable products, open each variation and set its price; a variable product where no variation has a price prints no offer either.

Variable products with prices get an `AggregateOffer` instead of an `Offer`:

```json
"offers": [{
  "@type": "AggregateOffer",
  "lowPrice": "39.00",
  "highPrice": "59.00",
  "offerCount": "3",
  "priceCurrency": "EUR",
  "availability": "http://schema.org/InStock",
  "url": "https://example-shop.hr/product/linen-shirt/"
}]
```

The report accepts `lowPrice` in place of `price`, and so does Google's merchant listing rich result, which reads `AggregateOffer` as a price range. You do not need a default variation for the schema; you need a price on the variations.

"Call for price", catalogue-mode plugins and B2B plugins that hide prices from guests are a different case. Whether they also strip the price from the schema depends on the plugin: some filter `woocommerce_structured_data_product` to remove the offer, some leave it. Test the product page in a private browser window, which is what the report and Googlebot see. If the price is hidden from visitors, it should not be in the markup either (Google requires the schema to match the visible page), and you accept that those products get no price in search.

### 3. Set the stock status

`availability` comes from `is_in_stock()`: in stock and on backorder both print `InStock`, out of stock prints `OutOfStock`. A product with no stock status set gets nothing, and that is the most common missing field on imported catalogues.

Product data → Inventory: either pick a **Stock status** (In stock, Out of stock, On backorder), or tick **Manage stock at product level** and enter a quantity so WooCommerce derives the status from it. For variable products, stock is per variation unless the parent manages it. A bulk edit on Products → All Products (select all → Edit → Stock status) fixes an imported catalogue in one save.

Out-of-stock products deserve a decision of their own: WooCommerce keeps them indexable and marks them `OutOfStock`, which Google shows as "Out of stock" under the listing. The WooCommerce checker's separate out-of-stock finding and the [WooCommerce store checklist](https://getreport.app/guides/woocommerce-store-checklist) cover what to do with them.

### 4. Enable reviews and let the rating come from real ones

`aggregateRating` appears only when three things are true: reviews are enabled site-wide, ratings are enabled on reviews, and the product has at least one approved review with a star rating.

1. WooCommerce → Settings → Products, section **Reviews**: tick **Enable product reviews** and **Enable star rating on reviews**.
2. On the product, Product data → Advanced: **Enable reviews** ticked.
3. Comments → approve the pending reviews. Reviews without stars (rating not required) do not count towards the average.

WooCommerce prints the average and the count, plus a few recent reviews as `review` entries. Do not add a rating by hand in an SEO plugin's schema fields for products that have no visible reviews; Google's review snippet rules require the reviews to be on the page, and a store caught with invented ratings loses review snippets across the site.

A review plugin (Judge.me, YITH, WP Product Review and similar) is where duplicates start. Some extend WooCommerce's block by filtering it; others print their own Product with an `aggregateRating`. If the schema types finding shows `Product ×2`, look in the plugin's settings for a "rich snippets", "schema" or "structured data" option and turn it off there, or turn WooCommerce's rating off, but keep the offer and the rating in the same Product.

### 5. Decide which plugin owns the Product block

The rule is one Product per page, carrying price, availability and rating together. Three plugins commonly touch it:

- **Yoast SEO** on its own leaves WooCommerce's block alone. With the **Yoast WooCommerce SEO** add-on, the Product becomes part of Yoast's graph and WooCommerce's separate block goes away. Either state is fine; the report counts Yoast as one generator.
- **Rank Math** has a WooCommerce section under Rank Math → General Settings where you choose whether to keep WooCommerce's schema or replace it with Rank Math's. Pick one.
- **Page builders** (Elementor Pro, Divi, WPBakery) with a custom single-product template sometimes do not fire the `woocommerce_single_product_summary` hook, which is where WooCommerce's block is generated. The product page then has no Product at all. Add the hook back in the template, use the builder's WooCommerce product widgets rather than static elements, or print the block yourself from the same product data.

If you have to switch WooCommerce's block off in code because another plugin is the source, this is the whole change, in a small plugin or the child theme's `functions.php`:

```php
<?php
// Let the SEO plugin be the single Product source; remove WooCommerce's block.
add_action('init', function () {
    if (function_exists('WC')) {
        remove_action('woocommerce_single_product_summary', [WC()->structured_data, 'generate_product_data'], 60);
    }
});
```

The opposite direction, keeping WooCommerce's block and adding to it, uses the filter WooCommerce provides. This example adds a `brand` from a product attribute:

```php
<?php
add_filter('woocommerce_structured_data_product', function (array $markup, WC_Product $product) {
    $brand = $product->get_attribute('pa_brand');
    if ($brand !== '') {
        $markup['brand'] = ['@type' => 'Brand', 'name' => $brand];
    }
    return $markup;
}, 10, 2);
```

Both snippets are reversible: delete them and WooCommerce's default behaviour returns.

### 6. Validate, then confirm with Google

Re-run the WooCommerce checker on the same product page. The finding should read "WooCommerce Product schema has price, availability and rating", the required-properties finding should pass for Product, and the eligibility finding should list Product. Then paste the URL into Google's Rich Results Test, which applies Google's own rules (currency format, image reachability, `priceValidUntil` warnings) on top of the presence checks. The [schema validator](https://getreport.app/tools/schema-validator) shows the same schema module on any page and is the tool to use on category and shop pages, where WooCommerce prints no Product block and none is expected.

> **Tip:**
> Test three products, not one: a simple product with reviews, a variable product, and a recently imported one. They exercise different code paths, and the imported one is where the stock status is usually missing.

## Platform notes

### WooCommerce blocks and the block theme

On block themes, the Single Product template is built from blocks (Product Price, Add to Cart with Options, Product Rating). WooCommerce still generates the schema from the product record, not from which blocks are present, so removing the Product Rating block from the template does not remove the rating from the schema. Removing the price block does not remove the price either. What matters is the data.

### Multi-currency and geolocation

Currency switchers that change the price per visitor print whichever currency the server decided on for that request. The report and Googlebot both fetch from a fixed location, so the schema they see carries that location's currency. Make sure the `priceCurrency` code and the `price` value come from the same switch; a €49.90 label with `"priceCurrency": "USD"` in the markup is a mismatch Google flags in Merchant Center.

### Headless WooCommerce

When another framework renders the storefront from the Store API, WooCommerce's schema never reaches the visitor. Generate the Product block in the front end from the same API response that renders the price, including `availability` from the `is_in_stock` field.

## Verify

- The WooCommerce checker's product schema finding passes on the three test products, and the technical line reports one Product entity per page.
- The schema types finding shows `Product` once, not `Product ×2`, and the duplicate-entities check does not list the product.
- Google's Rich Results Test shows "Merchant listings" with no errors; warnings about `priceValidUntil` or `shippingDetails` are optional fields, not blockers.
- Search Console → Enhancements → Merchant listings (or Product snippets) stops reporting "Missing field 'availability'" or "Missing field 'price'" for those URLs over the next crawl.

## Common mistakes

- **Sale price filled, regular price empty.** WooCommerce treats it as no price, and `offers` disappears. Fill the regular price and put the discount in the sale price field.
- **Stock managed nowhere.** Neither a stock status nor a managed quantity: no `availability`. Bulk-edit the stock status after every import.
- **Rating typed into an SEO plugin's schema fields.** The product has no visible reviews, Google's rules require them on the page, and the whole site can lose review snippets. Enable reviews and wait for real ones.
- **Rank Math and WooCommerce both printing a Product.** The page carries two entities with the same name and URL; Google picks one, and often the one without the rating. Choose the source in Rank Math's WooCommerce settings.
- **Builder template with no product summary hook.** The page looks right and has no schema at all. Use the builder's WooCommerce widgets or add the hook back.
