Short answer. Google keeps showing a stale price when the product's structured data has no priceValidUntil, or carries one with a date already in the past. Shopify's built-in structured_data filter does not emit that field at all, so most themes ship without it. Add a future-dated priceValidUntil to the offer, and make sure promotions refresh it instead of leaving an expired date behind.
You search your own product on Google, or check a Shopping result, and the price shown doesn't match what's actually on your Shopify store — sometimes an old price from before your last change, sometimes no price rich result at all. Nothing is wrong in Shopify admin: the product is active, the price field is correct, checkout charges the right amount. The mismatch only exists on Google's side.
Merchants hit this and get told to wait. In a Shopify Community thread titled "Google Still Showing Old Prices for Sold-Out Products (Shopify & Merchant Center Issue)" (27 February 2025), the advice offered is "It sounds like Google is caching the old prices, which can take time to update," followed by requesting a recrawl and refreshing the feed. That treats the symptom. It leaves untouched the structural question of why Google had no reason to treat your price as time-sensitive in the first place — which is usually the missing field below.
What priceValidUntil is, and why Shopify leaves it out
priceValidUntil is a property of the Offer object inside Product structured data. Schema.org defines it in a single line — "the date after which the price is no longer available" — and it expects a date. It is the only field in your markup that tells Google how long the price it just read should be treated as current.
Google lists it as a recommended property, and specifically under merchant listing structured data — the markup that applies to pages where a shopper can actually buy from you. It is absent from Google's product snippet guidance, which is part of why it gets missed: if you learned Product schema from the snippet documentation, you never met this field at all.
Shopify does not add it for you. The built-in structured_data Liquid filter that Dawn and most modern themes use to generate Product JSON-LD emits an offers object with @id, @type, availability, price, priceCurrency and url. Shopify's own documentation shows the complete output, and priceValidUntil is not among those fields.
Is a missing priceValidUntil an error, or just a warning?
Worth settling before you panic-edit a theme, because the answer changes how urgently you should treat it. Google's Rich result report overview splits every reported issue into two kinds. Critical issues invalidate the item: "An invalid item has at least one critical issue preventing it from appearing as a rich result." Everything else is non-critical, and the report puts those in a separate table it calls "Improve item appearance" — which, in Google's words, "Shows non-critical issues affecting structured data on your site."
A recommended property like priceValidUntil falls on the non-critical side. Google's own definition of a valid item is explicit: "A valid item is an item that doesn't have any critical issues and can appear on Google as a rich result." So a product missing this field is still eligible for the rich result, and the report is not telling you the page is broken.
That is genuinely reassuring, and it is also exactly why the field gets ignored for years. It never turns anything red. It sits in the improvement table, below the errors, on a report most merchants open only when something has already gone wrong — and the one thing it controls is how long Google treats your price as current. An expired date is the sharper version of the same trap, and that one Google does act on: see cause 2 below.
How to confirm it yourself in 5 minutes
- Run a
site:search for the affected product URL and compare the price shown in the snippet against your live storefront price. - Paste the same URL into Google's Rich Results Test and open the detected
Productmarkup — check theoffersobject for apriceValidUntilfield and whether its date is in the future. - In Google Search Console, open Enhancements → Merchant listings and read the "Improve item appearance" table, not just the error list — a missing recommended field is reported there rather than as an error, which is why it is easy to scroll past.
- If you also run Merchant Center for Shopping ads, check its Diagnostics tab for "mismatched value (price)" — that's a related but distinct feed-level issue, not the same as this Search-side symptom.
Want this checked automatically? We inspect the Offer/price schema on every product page in your catalog — read-only, no admin access, about 2 minutes. → free scan
Five causes, and how to tell which one is yours
1. priceValidUntil is missing from the product's structured data entirely. This is the default state of a stock Shopify theme, not a merchant mistake: as above, the field simply isn't among the ones Shopify's built-in filter emits, so it has to be added deliberately via a theme edit or an app. Start here, because on most stores it is true before you check anything else. Without it, Google's Offer object has no explicit signal for how long the current price should be trusted, and per Google's own merchant listing documentation, it's a recommended property of the Offer type — it isn't required for basic eligibility, but its absence removes one of the freshness signals Google uses to decide whether to trust and (re)display a price.
2. priceValidUntil was set for a past promotion and never updated. A sale or discount app writes a priceValidUntil matching the promo's end date, the sale ends, and nothing goes back to refresh or remove that date. Google's documentation is explicit here: "Your listing may not display if the priceValidUntil property indicates a past date." For sale pricing Google asks you to "provide both a start and an end date/time to clearly define the sale period" — the start coming from validFrom, the end from validThrough or priceValidUntil — and to keep the start earlier than or equal to the end. A leftover expired date is worse than having none.
3. Google simply hasn't recrawled since your last price change. Indexing lag alone can produce a stale price for days to weeks, and it is a genuine explanation — it is the one the thread above lands on. But it compounds with cause 1: a page with no freshness signal in its Offer data gives Google less reason to treat the price as time-sensitive and prioritize a recheck.
4. Malformed price or currency fields. Less common, but worth a quick check: price must be a bare number string with no currency symbol, and priceCurrency must be a valid ISO 4217 code. A stray symbol or the wrong code can make Google discard the price signal from your structured data entirely and fall back to whatever it last crawled from the visible page.
5. The discounted price is in priceSpecification, but Google is reading price. Worth a look whenever a sale is running and Google is showing the pre-sale figure. Google's rule is unambiguous: "If you use both the offers.price and offers.priceSpecification properties to encode an active price, Google will use the price provided through the offers.price property and ignore the offers.priceSpecification property." So a discount app that writes the sale price into priceSpecification while leaving the original in price produces exactly this symptom — a correct storefront, a correct checkout, and a stale number on Google.
The other offer fields Shopify leaves out — and why they are not the same problem
The gap that produces a missing priceValidUntil is not specific to the price. Google's merchant listing documentation lists priceValidUntil, shippingDetails and hasMerchantReturnPolicy as recommended properties of the same object, Offer. Shopify's structured_data filter emits exactly six keys inside offers — @id, @type, availability, price, priceCurrency and url. None of the recommended three is among them. On a stock theme they are absent by construction, and they turn up together in the same "Improve item appearance" table.
Google publishes the message they produce. Its rich result error messages page gives the template as Missing field "property name", with a one-line remedy: "Add the missing property name. This can also occur if the name is an empty string." Read literally, that is three fields and one fix. It is not, and the difference decides whether a theme edit is worth making.
For priceValidUntil, the remedy is the whole story — it is the case this article covers, and the fix below is all of it. For the other two, Google documents an override chain that the price field has none of. The shipping policy documentation publishes a fixed order of precedence — the Content API first, then settings in Merchant Center or Search Console, then product-level markup, then organization-level markup — and states the consequence outright: "if you provide both shipping policy markup on your site and shipping policy settings in Search Console, Google will only use the information provided in Search Console."
So a merchant pastes a shipping block into the theme, the warning stays, and nothing in the message distinguishes "you did not provide this" from "you provided it and Google used a higher-precedence source instead". The template is a bare field name either way. Those two fields, the precedence order in full, and the Shopify shipping-profile trap behind them are covered here; this section exists so you do not spend an afternoon editing a theme for a field your markup was never going to control.
The fix
- Add priceValidUntil to your theme's Product JSON-LD if it isn't there, computed live rather than hardcoded — set it to a rolling window from the current date, recalculated on every render, so it never quietly goes stale on its own:
(the example above adds 30 days; a sale-price app should instead set this to the actual promo end date, and clear or extend it the moment the sale is edited.)"priceValidUntil": "{{ 'now' | date: '%s' | plus: 2592000 | date: '%Y-%m-%d' }}" - Audit any sale/discount app for a leftover expired date — check its settings for a hardcoded promo end date that outlived the promo itself.
- Request reprocessing in Search Console's URL Inspection tool for the affected URL once the fix is live, rather than only waiting for the next organic crawl.
- Validate
priceandpriceCurrencyformatting with the Rich Results Test while you're in there. - Check that the active price sits in
offers.price, not only inoffers.priceSpecification— if both carry an active price, Google reads the first and ignores the second. - If you run sale prices, declare
validFromas well as the end date — Google asks for both a start and an end to define the sale period, so a promotion carrying only an end date leaves the window half-declared. The start goes invalidFrom, the end invalidThroughorpriceValidUntil, with the start earlier than or equal to the end. - Do not paste shipping or returns markup as a reflex. For
shippingDetailsandhasMerchantReturnPolicy, check first whether a Merchant Center or Search Console setting is already supplying them — per the precedence order above, that source wins over anything in your theme, so the theme edit can leave the warning exactly where it was.
None of this shows up as an error anywhere in Shopify admin — the product looks completely correct on your side. The only visible symptom is a shopper-facing price on Google that quietly drifted out of sync with your store, which is exactly the kind of gap that costs a sale before the shopper ever reaches checkout.
Next in this cluster
- The other fields the built-in filter omits: shippingDetails and hasMerchantReturnPolicy
- The other price failure: a product declaring zero, and rejected outright
- When the whole block fails: "Unparsable structured data" on Shopify
- Which system dropped your product? The 11 failure modes, triaged
- Products disapproved in Google Merchant Center: the on-page price mismatch trap
- Shopify says in stock, Google Shopping shows sold out — the availability mismatch
- Limited performance due to missing identifiers: the GTIN, MPN and brand fields Shopify leaves blank
- "Duplicate, Google chose different canonical than user" on Shopify products
Not sure if this is happening on your store right now?
Scan my store for free