Your Product schema is invalid and you don't know it - the price string gotcha
The Trap
If you've added Product structured data, watched it pass a JSON linter, and still got "Invalid" in Search Console (or no price shown in rich results), you've almost certainly hit the one that catches everyone: the price field. Valid JSON is not valid schema.org. Here's the trap and the fix.
The markup that looks right but isn't:
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Aeron Chair",
"offers": {
"@type": "Offer",
"price": "$1,299.00",
"priceCurrency": "USD"
}
}
That's perfectly valid JSON. It also fails Google's Product validation, because price has rules that JSON itself knows nothing about:
- No currency symbol.
$belongs inpriceCurrency, not in the number. - No thousands separator. The
,makes it unparseable as a number. - Dot is the only decimal mark.
1.299,00(common in the EU) is rejected.
So "$1,299.00" is three violations in one string. Google either drops the offer or flags the whole item invalid.
The Fix
"offers": {
"@type": "Offer",
"price": "1299.00",
"priceCurrency": "USD"
}
price is the bare number as a string; the symbol lives entirely in priceCurrency as an ISO 4217 code (USD, EUR, SGD, โฆ). Schema.org's own note is explicit about this - it even warns against Unicode symbols like U+00A0.
If your price comes out of a formatter, strip it before it goes into JSON-LD, don't reuse the display string:
// display value: "$1,299.00" - do NOT ship this to JSON-LD
const display = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
}).format(1299);
// what the schema actually needs:
const priceForSchema = (1299).toFixed(2); // "1299.00"
The Three Neighbors That Break With It
Once the price parses, these are the next ones Search Console complains about:
priceCurrencyis required inside everyOffer. A price with no currency is meaningless, and Google treats it as an error, not a warning.availabilitymust be a schema.org URL, not a word.// wrong "availability": "in stock" // right "availability": "https://schema.org/InStock"The enum values are
InStock,OutOfStock,PreOrder,BackOrder, and a handful more - always the full URL.priceValidUntilwants ISO 8601. Not"31 Dec 2026", but"2026-12-31". Omit it and Google will nudge you to add it for fixed-price offers.
Why the JSON Linter Can't Catch Any of This
A JSON linter checks syntax - brackets, commas, quotes. Schema.org rules are semantic - "this string must be a bare decimal", "that string must be one of these URLs". They live in the vocabulary, not the grammar, so a green checkmark in your editor tells you nothing about whether Google will accept the markup. That's the gap you have to close with an actual structured-data validator that understands the schema.org types, not just JSON. I keep a small free schema markup validator around for exactly this - paste the JSON-LD, and it flags the price/currency/enum problems that a plain linter waves through.
The Short Version
price= bare number as a string:"1299.00". No symbol, no thousands separator,.for decimals only.- Currency goes in
priceCurrencyas an ISO 4217 code. availabilityis a fullschema.org/...URL, and dates are ISO 8601.- Valid JSON โ valid schema. Validate against the vocabulary, not just the syntax.
Top comments (1)
A JSON linter passing gave me false confidence more than once ๐ . Rich Results testing caught the real issues-especially formatted prices and availability values. Great reminder to validate the schema, not just the JSON.
Comments
No comments yet. Start the discussion.