Sale price structured data: mark up Black Friday deals
Last updated:
To show a sale correctly in Product structured data, put the price shoppers pay now in offers.price, add the original price as a strikethrough price, and give the sale a start and end date. Search engines can then read the deal as a fact instead of guessing from your page design, which matters most during peak sales like Black Friday.
How sale price markup works
Google's merchant listing documentation describes the pattern:
- The active price stays in
offers.priceandoffers.priceCurrency. Don't give it apriceType. - The original price goes in
offers.priceSpecification, as aUnitPriceSpecificationwithpriceTypeset tohttps://schema.org/StrikethroughPrice(the only value supported there). - When that second price is present, the active price is treated as a sale price.
- Dates:
validFromandvalidThroughgive the start and end date-time of the price. You can usepriceValidUntilinstead ofvalidThroughfor the end, but use one or the other. All dates use ISO 8601, with a time zone.
Example
A shirt reduced from 79 € to 59 € for one week:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Linen shirt, sky blue",
"image": "https://your-store.com/img/linen-shirt-front.jpg",
"offers": {
"@type": "Offer",
"url": "https://your-store.com/products/linen-shirt-sky",
"price": "59.00",
"priceCurrency": "EUR",
"availability": "https://schema.org/InStock",
"validFrom": "2026-11-23T00:00:00+01:00",
"priceValidUntil": "2026-11-30T23:59:59+01:00",
"priceSpecification": {
"@type": "UnitPriceSpecification",
"priceType": "https://schema.org/StrikethroughPrice",
"price": "79.00",
"priceCurrency": "EUR"
}
}
}
</script>
Common mistakes
- Swapped prices. The original price ends up in
offers.priceand the sale price nowhere. The markup then contradicts the page. - Markup out of sync with the page. The page shows the deal but the JSON-LD is cached from before the sale, or the reverse after it ends. Generate both from the same product data.
- No end date. Without
validThroughorpriceValidUntil, nothing in the markup says the deal is temporary. - Both end properties at once, with different dates.
- A strikethrough price nobody ever paid. In the EU, a price reduction announcement must show the prior price, which is the lowest price applied in the 30 days before the reduction. Your crossed-out price should follow the same rule.
Keep your feed consistent
If you also send products to Google Merchant Center, the feed has its own sale attributes (sale_price and sale_price_effective_date). Make sure the feed, the page and the markup show the same prices for the same dates, so shoppers never land on a deal that no longer matches the ad or listing.
Sale markup builds on a complete Product structured data block. For the rest of your peak-season preparation, see the Black Friday checklist.
How Averifly helps
Averifly's catalog rules check that your product pages carry Product structured data with a price, currency and availability, and that a price is visible on the page. Run a free scan before your sale starts, and see our rules for the full list of checks.
Frequently asked questions
How do I mark a sale price in Product structured data?
Keep the current sale price in offers.price and add the original price as a second price in priceSpecification, with priceType set to https://schema.org/StrikethroughPrice. Google treats the active price as a sale price when that second price is present.
Should I use priceValidUntil or validThrough for the end of a sale?
Use one of them, not both. validFrom and validThrough give the start and end date-time of the price; priceValidUntil gives the date after which the price is no longer available. All use ISO 8601 format.
Do I need to remove the sale markup after the sale ends?
Yes. When the sale ends, the page and the markup should both return to the regular price. An end date helps, but your platform should still stop outputting the old sale price.
