Product Schema priceValidUntil Missing: Rich Snippet Fix

Google flagging Product schema for missing priceValidUntil and dropping price rich snippets? The exact field, format, and Next.js fix that restores them.
SEOSchemaNext.js
May 30, 20265 min read968 words

The Problem

Got a Search Console notification on a client store last week:

Invalid items detected on https://example.com
Issue type: Product snippets
"Either offers, review or aggregateRating should be specified"
"Missing field 'priceValidUntil' (optional)"
Pages affected: 1,247

The first warning was familiar. The second is newer. The pages still ranked. They just lost their price snippets in the SERP. The little grey "$49.99 — In Stock" line under the title disappeared, and CTR on those listings dropped 14% week-over-week in GSC.

The Rich Results Test confirmed it:

Product
  offers: Offer
    price: 49.99
    priceCurrency: USD
    availability: InStock
    ⚠ priceValidUntil: Missing

Both the Schema.org Offer specification and Google's Product structured data guidelines now treat priceValidUntil as effectively required for any product that emits price. The "optional" label in the warning is a holdover from older docs. The rich snippet eligibility is not optional.

Why It Happens

priceValidUntil tells Google how long the price is guaranteed. Without it, the price might be stale, so Google withholds the price snippet rather than show a number that has changed. The behaviour rolled out across Google search in early 2026, after the December 2025 structured data update.

Two reasons the field goes missing in practice.

One: most Next.js commerce starters generate Product JSON-LD from the product database, and the database has no concept of a price-expiry date. The Offer block emits price, priceCurrency, and availability, and that is the whole shape of the schema. There is no field to map.

Two: even when a priceValidUntil is supplied, the format is wrong half the time. Google requires ISO 8601 with a date component only, not a full datetime, and the date must be in the future. A Unix timestamp, a Date object, or anything in the past is silently dropped from the rich snippet eligibility check. The Rich Results Test still shows it as "present", but the SERP treats it as absent.

The Fix

Decide what your priceValidUntil means, generate it in ISO 8601 date format, and refresh it whenever the page is cached or the price changes.

1. Pick a price-validity window. Most stores treat prices as valid until the next scheduled review. For a store that audits prices weekly, one week out is the right answer. For a store on long-term contract pricing, six months. The number is a business decision, not a technical default. Pick it deliberately:

// lib/schema.ts
export function getPriceValidUntil(daysOut = 30): string {
  const date = new Date()
  date.setDate(date.getDate() + daysOut)
  return date.toISOString().split('T')[0]
}

The .split('T')[0] matters. Google accepts 2026-06-29. Google does not consistently accept 2026-06-29T14:32:00.000Z. The time component fails the structured data check on some Product variants and warns "unrecognized format" on others. ISO date only.

2. Emit the field in your Product JSON-LD. In the App Router product page:

import { getPriceValidUntil } from '@/lib/schema'

export default async function ProductPage({ params }: Props) {
  const { slug } = await params
  const product = await getProduct(slug)

  const schema = {
    '@context': 'https://schema.org',
    '@type': 'Product',
    name: product.name,
    description: product.description,
    image: product.images,
    sku: product.sku,
    brand: { '@type': 'Brand', name: product.brand },
    offers: {
      '@type': 'Offer',
      price: product.price.toFixed(2),
      priceCurrency: product.currency,
      availability: product.inStock
        ? 'https://schema.org/InStock'
        : 'https://schema.org/OutOfStock',
      priceValidUntil: getPriceValidUntil(30),
      url: `https://example.com/product/${product.slug}`,
    },
  }

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
      />
      <ProductDetail product={product} />
    </>
  )
}

The priceValidUntil is rendered at request time, so it is always 30 days from today. If you cache the page with 'use cache', set cacheLife('hours') so the date refreshes daily. A priceValidUntil of 2026-04-01 sitting on a page that has been cached for two months will fail validation again, even though the code was correct the day it deployed.

3. Update the field when the price changes. When a price is edited in the CMS, invalidate the cache so the new validity window kicks in:

// app/api/product/[id]/route.ts
import { revalidateTag } from 'next/cache'

export async function PATCH(req: Request, { params }: Props) {
  const { id } = await params
  await db.product.update({ where: { id }, data: await req.json() })
  revalidateTag(`product:${id}`)
  return Response.json({ ok: true })
}

The next render computes a fresh priceValidUntil from the current date, and the snippet stays valid.

4. Validate before deploying. Run the changed pages through the Rich Results Test:

curl "https://search.google.com/test/rich-results?url=https://example.com/product/widget"

You want a green Product block with offers.priceValidUntil listed and no warnings. After 24-48 hours, GSC's Product enhancement report should show the affected pages moving from "Invalid" to "Valid". On the client store above, the price snippets reappeared in the SERP within four days, and CTR recovered to the pre-warning baseline within ten.

The Lesson

priceValidUntil is required in practice for Product rich snippets in 2026. Format it as an ISO 8601 date with no time component, set it in the future based on your price-review cadence, and refresh it whenever you cache the page or update the price. Without it, the schema still validates as a Product, but the price line disappears from the SERP and the CTR loss is real.

If your Product schema is throwing GSC warnings and your CTR is sliding, that is an audit I run for ecommerce clients. See my services. For a related Product schema fix, read Product schema return policy missing.

Want a complete schema audit? Get it scheduled.

Back to blogStart a project