Product Schema Missing Offers Field: Fix Offers, priceValidUntil & Availability Warnings
A “missing offers” message means Google found a Product object with no usable Offer attached to it, or found an Offer without a valid price and currency. Missing priceValidUntil and availability are warnings, not errors, so they do not block eligibility on their own. Fix the Offer first, then add the recommended fields.
Required vs. recommended Product properties
Google treats Product markup as two separate features: product snippets and merchant listings. Each has its own required fields, and the Rich Results Test reports on both when it detects them. This is why a page can pass one feature and fail the other.
The root cause of most “missing field” reports is that the markup is valid JSON-LD but incomplete for the feature Google is evaluating. The parser does not guess. If offers is absent, or offers.price is not a recognizable number, the item is flagged.
Google’s Product structured data documentation separates properties into required (a missing one produces an error and makes the item ineligible) and recommended (a missing one produces a warning and the item stays eligible). The table below summarizes the fields that matter most for stores. Always confirm against the current documentation, because Google updates these pages.
| Property | Product snippets | Merchant listings | Missing it causes |
|---|---|---|---|
name | Required | Required | Error |
offers (or review / aggregateRating) | One of the three required | offers required | Error |
image | Recommended | Required | Error for merchant listings |
offers.price | Required inside Offer | Required | Error |
offers.priceCurrency | Required inside Offer | Required | Error |
offers.availability | Recommended | Recommended | Warning |
offers.priceValidUntil | Recommended | Recommended | Warning |
brand, sku, gtin, description | Recommended | Recommended | Warning |
Two details are easy to miss. A Product with only review or aggregateRating can pass the snippet check without offers, but it cannot qualify for merchant listings. And offers is not enough by itself: an Offer object with no price still produces an error.
Valid availability values
The availability property takes an ItemAvailability value written as a full schema.org URL. Google’s documentation lists these:
https://schema.org/InStockhttps://schema.org/OutOfStockhttps://schema.org/PreOrderhttps://schema.org/BackOrderhttps://schema.org/SoldOuthttps://schema.org/OnlineOnlyhttps://schema.org/LimitedAvailabilityhttps://schema.org/InStoreOnlyhttps://schema.org/Discontinuedhttps://schema.org/PreSale
Values such as "In stock" or "instock" are not in the documented format. The value should also match what the visible page says. Markup that reports InStock while the page shows “Sold out” violates Google’s guideline that structured data must reflect page content.
The usual causes of Offer errors
Offer errors usually come from one of two sources: a malformed price, or two competing Product objects on the same URL.
Price formatted for humans instead of parsers
Themes often print the display price straight into the schema. The display price contains characters a parser cannot read as a number.
| Value in markup | Problem | Valid value |
|---|---|---|
"$1,299.00" | Currency symbol and thousands separator | "1299.00" |
"1.299,00" | European separators | "1299.00" |
"USD 49" | Currency code inside the price | "49.00" with "priceCurrency": "USD" |
"From 20" | Text in a numeric field | "20.00" |
The rule is simple. price holds digits and one period as the decimal separator. priceCurrency holds the three-letter ISO 4217 code (USD, EUR, GBP, BDT) in a separate property.
Duplicate Product markup from the theme and an SEO plugin
Both the theme and the SEO plugin can emit their own <script type="application/ld+json"> block with "@type": "Product". WooCommerce core outputs Product JSON-LD by default, and SEO plugins and Shopify apps can add a second block. The result is two Product objects on one URL, often with different data: one complete, one missing offers.
The Rich Results Test then lists multiple Product items, and the incomplete one raises the error. To find the source:
- Open the product page, choose View Source, and search for
"@type":"Product". - Count the matches. More than one means duplicates.
- Identify which plugin or theme file prints each block.
- Disable Product schema output in all but one source, using that plugin’s schema settings or the theme’s structured data option.
Mixed formats cause the same problem. A theme that outputs Product microdata in the HTML while a plugin adds JSON-LD still creates two entities.
Building a valid JSON-LD block
The workflow uses three GetEasyTool utilities in sequence. The product used below is a merino sweater priced at $1,299.00 on the display page, which makes it a good test of the price rule.
Step 1: Write the description. Open the SEO Product Description AI and enter the product facts: “Merino crew neck sweater, 100% merino wool, machine washable, unisex, sizes S to XL.” The output is a two-sentence description you can copy into the schema’s description field. This avoids the common mistake of pasting marketing copy with HTML tags into JSON.
Step 2: Generate the page title and meta description. Open the Shopify/WooCommerce Meta Generator and enter the product name and primary benefit. Use its output for the <title> and meta description tags, and keep the product name identical to the name value in the schema. Mismatched names between the page and the markup are a frequent source of reconciliation problems in Search Console.
Step 3: Build the JSON-LD. Open the Product FAQ Accordion & Schema Builder and fill in the fields. With the inputs below, the output is a block like this:
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Merino Crew Neck Sweater",
"image": ["https://example.com/images/merino-sweater.jpg"],
"description": "A 100% merino wool crew neck sweater. Machine washable and available in sizes S to XL.",
"sku": "MCN-001",
"brand": { "@type": "Brand", "name": "Northfield" },
"offers": {
"@type": "Offer",
"url": "https://example.com/products/merino-crew-neck-sweater",
"price": "1299.00",
"priceCurrency": "USD",
"priceValidUntil": "2026-12-31",
"availability": "https://schema.org/InStock",
"itemCondition": "https://schema.org/NewCondition"
}
}
Here is what each field does:
nameandimage:nameis required for both features.imageis required for merchant listings, so supply at least one absolute URL.skuandbrand: Recommended identifiers. Addgtinas well if the product has a barcode.offers.price: The input was displayed as $1,299.00. The output is"1299.00"because the symbol and comma are stripped. This is the field behind most price errors.offers.priceCurrency:"USD", entered separately from the price.offers.priceValidUntil: A date in ISO 8601 format. Set it to the end of a sale, or a date far enough ahead that it does not expire between template updates. An expired date is a stale-data risk.offers.availability: The dropdown value “In stock” is output as the full URLhttps://schema.org/InStock.
Paste the block into the product template once, and make sure no other source is outputting a second Product object.
Where FAQPage markup stands
The builder can also produce FAQPage JSON-LD and the matching HTML accordion. Since August 2023, Google restricts FAQ rich results to well-known, authoritative government and health websites. A typical store will not see the expandable FAQ result in Search even with valid markup.
The on-page accordion is still worth keeping. It answers sizing, shipping, and returns questions where buyers are deciding, and the text is crawlable page content. Treat FAQPage markup as optional, and keep the accordion for users.
<rect x="215" y="80" width="165" height="150" rx="12" fill="#FFFFFF" stroke="#7C3AED" stroke-width="2"/>
<circle cx="297" cy="110" r="16" fill="#7C3AED"/>
<text x="297" y="116" font-size="16" font-weight="700" fill="#FFFFFF">2</text>
<text x="297" y="150" font-size="15" font-weight="700" fill="#2E1065">Build Offer</text>
<text x="297" y="172" font-size="12" fill="#4B5563">price: "1299.00"</text>
<text x="297" y="190" font-size="12" fill="#4B5563">priceCurrency: USD</text>
<text x="297" y="208" font-size="12" fill="#4B5563">availability: URL</text>
<rect x="400" y="80" width="165" height="150" rx="12" fill="#FFFFFF" stroke="#7C3AED" stroke-width="2"/>
<circle cx="482" cy="110" r="16" fill="#7C3AED"/>
<text x="482" y="116" font-size="16" font-weight="700" fill="#FFFFFF">3</text>
<text x="482" y="150" font-size="15" font-weight="700" fill="#2E1065">Test</text>
<text x="482" y="172" font-size="12" fill="#4B5563">Rich Results Test</text>
<text x="482" y="190" font-size="12" fill="#4B5563">Errors: must be 0</text>
<text x="482" y="208" font-size="12" fill="#4B5563">Warnings: optional</text>
<rect x="585" y="80" width="185" height="150" rx="12" fill="#7C3AED"/>
<circle cx="677" cy="110" r="16" fill="#FFFFFF"/>
<text x="677" y="116" font-size="16" font-weight="700" fill="#7C3AED">4</text>
<text x="677" y="150" font-size="15" font-weight="700" fill="#FFFFFF">Monitor</text>
<text x="677" y="172" font-size="12" fill="#EDE9FE">Search Console</text>
<text x="677" y="190" font-size="12" fill="#EDE9FE">Merchant listings</text>
<text x="677" y="208" font-size="12" fill="#EDE9FE">Validate fix</text>
Validation and monitoring
Rich Results Test vs. Schema Markup Validator
The two tools answer different questions. Use both, in this order.
| Rich Results Test | Schema Markup Validator | |
|---|---|---|
| Checks | Eligibility for Google-supported features | Syntax and vocabulary against schema.org |
| Product-specific rules | Yes: required and recommended fields per Google | No: it does not apply Google’s requirements |
| Output | Errors and warnings per detected feature | Parsed entities and syntax problems |
| Best use | Final check before and after deployment | Debugging structure, nesting, or non-Google types |
A block can pass the Schema Markup Validator and still fail the Rich Results Test. The validator confirms the markup is valid schema.org. It does not confirm that Google’s required fields are present. Test the live URL rather than pasted code, because that is the only way to catch duplicate blocks injected by plugins.
Reading the Merchant listings report
In Search Console, open the Shopping section and select Merchant listings. The report appears only after Google has detected merchant listing markup on the property. It groups affected pages by issue and splits them into critical issues, which make items ineligible, and non-critical issues, which are warnings.
Read it in this order:
- Start with critical issues such as missing
offers.priceorpriceCurrency. These remove pages from merchant listing eligibility. - Open an issue to see the affected URLs and test one with the Rich Results Test to reproduce it.
- Fix the template, then use Validate Fix. Google re-crawls the affected URLs, so confirmation can take days.
- Treat non-critical issues like missing
priceValidUntilas a second pass.
The separate Product snippets report tracks the snippet feature. A fix to the Offer object usually clears issues in both.
Final tip: Add one cheap regression check to your release routine. After any theme update or SEO plugin update, run the Rich Results Test on one product URL and search the page source for "@type":"Product". Updates often re-enable a second schema output without notice, and this takes under a minute.
Frequently asked questions
Is priceValidUntil required for Product structured data?
No. Google lists priceValidUntil as a recommended property, so omitting it produces a warning in the Rich Results Test, not an error. Merchant listing eligibility depends on name, image, price, and priceCurrency. Add a future date for time-limited sale prices.
What are the valid availability values for Product schema?
Use full schema.org URLs such as https://schema.org/InStock, OutOfStock, PreOrder, BackOrder, SoldOut, OnlineOnly, LimitedAvailability, InStoreOnly, Discontinued, or PreSale. Plain text like 'In stock' is not the documented format and may not be recognized. Match the value to the page's actual stock status.