Shopify Structured Data
Flash sale structured data schema
for Shopify and AI shopping agents
How to implement priceValidUntil, sale price markup, and availability signals so AI agents surface your time-limited Shopify deals — and stop showing stale sale prices after the sale ends.
product.compare_at_price (not automatic discounts) for sales you want AI agents to read. Add priceValidUntil from a metafield storing the sale end date. Use availability: LimitedAvailability for low-stock urgency. Automatic discounts are checkout-only and invisible to server-rendered JSON-LD — AI agents can't read them.
Why flash sales are invisible to AI agents by default
Shopify merchants run two types of price reductions: compare-at price (strikethrough price set in the product admin) and automatic discounts (applied via Shopify Scripts, Shopify Functions, or manual discount codes). These behave very differently in structured data:
| Sale method | Reflected in product.price? | AI-agent readable? | Best for |
|---|---|---|---|
| Compare-at price | Yes — product.price = sale price | Yes | Sitewide sales, seasonal discounts, permanent markdowns |
| Automatic discount (Shopify Functions) | No — applied at checkout only | No | BOGO, volume tiers, member pricing |
| Discount codes | No — applied at cart/checkout | No | Email campaigns, influencer codes |
| Shopify Scripts (Plus only) | No — applied at checkout | No | Complex pricing rules |
The rule is simple: if the price reduction appears in product.price via compare-at, it can go into JSON-LD. If it's applied at checkout, it cannot.
Implementing priceValidUntil in Shopify
priceValidUntil tells AI agents and search engines when the current price expires. Without it, AI agents assume the price is indefinitely valid. With a past date, they may suppress the product from sale queries.
Step 1: Create a sale end date metafield
Create a metafield definition in the Shopify admin under Settings → Custom Data → Products:
- Namespace:
sale - Key:
end_date - Type: Date
- Name: Sale end date
Set this value on each product when you start a sale. Leave it blank when the product is at full price.
Step 2: Output priceValidUntil conditionally in Liquid
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "{{ product.title | escape }}",
"offers": {
"@type": "Offer",
"price": "{{ product.price | money_without_currency | remove: ',' }}",
"priceCurrency": "{{ cart.currency.iso_code }}",
"availability": "{% if product.available %}https://schema.org/InStock{% else %}https://schema.org/OutOfStock{% endif %}",
"url": "{{ shop.url }}{{ product.url }}"
{% if product.compare_at_price > product.price %}
,"priceSpecification": {
"@type": "UnitPriceSpecification",
"price": "{{ product.price | money_without_currency | remove: ',' }}",
"priceCurrency": "{{ cart.currency.iso_code }}",
"referenceQuantity": { "@type": "QuantitativeValue", "value": 1 }
}
{% if product.metafields.sale.end_date != blank %}
,"priceValidUntil": "{{ product.metafields.sale.end_date }}"
{% endif %}
{% endif %}
}
}
</script>
Simpler pattern with compare-at and priceValidUntil
{
"@context": "https://schema.org",
"@type": "Product",
"name": "{{ product.title | escape }}",
"offers": {
"@type": "Offer",
"price": "{{ product.price | money_without_currency | remove: ',' }}",
"priceCurrency": "{{ cart.currency.iso_code }}",
{% if product.compare_at_price > product.price %}
"priceValidUntil": "{{ product.metafields.sale.end_date | default: '' }}",
{% endif %}
"availability": "{% if product.available %}https://schema.org/InStock{% else %}https://schema.org/OutOfStock{% endif %}"
}
}
Low-stock urgency: using LimitedAvailability
Schema.org's LimitedAvailability value signals that a product has limited remaining inventory — the structured data equivalent of "Only 3 left!" When you want AI agents to surface urgency for a flash sale clearing old stock:
{% assign inventory = product.selected_or_first_available_variant.inventory_quantity %}
{
"availability": "{% if inventory > 10 %}https://schema.org/InStock
{% elsif inventory > 0 %}https://schema.org/LimitedAvailability
{% else %}https://schema.org/OutOfStock{% endif %}"
}
Use LimitedAvailability when inventory is low (threshold is yours to define — common choices are <5 or <10 units). Do not use it for all products on sale; reserve it for genuine low-stock situations. AI agents and Google treat it as a real signal — overuse reduces trust.
AI agent behavior for flash sale queries
| Query | No sale schema | With priceValidUntil | With LimitedAvailability |
|---|---|---|---|
| "[Brand] sale" / "[Product] discount" | Current price shown, no sale signal | Sale flagged; price and expiry cited | Urgency signal added if inventory low |
| "Best deals on [product category] right now" | Full-price products mixed with sale | Sale products surface first in deal-intent queries | Low-stock products surface with urgency note |
| "[Brand] flash sale today" | No structured signal to confirm sale is today | priceValidUntil confirms timing to AI agent | — |
| Past sale (priceValidUntil expired) | Old sale price may persist in cached results | AI suppresses expired offer from sale queries | — |
Stale sale price — the most damaging flash sale structured data error
If you set priceValidUntil to a past date, AI agents and Google interpret the current price as expired. This can suppress your product from all queries or, worse, confuse shoppers with outdated deal information in AI answers.
The clean solution: always set priceValidUntil from a metafield, never hard-code it. When the sale ends, clear the metafield — the conditional Liquid will omit the property entirely, which is the safest state.
Combining flash sale schema with Google Merchant Center promotions
If you use Google Merchant Center, note that Promotions in GMC and priceValidUntil in structured data are independent signals. GMC promotions control Shopping ad display. Structured data controls organic product rich results and AI agent knowledge graphs. You want both:
- GMC promotion for Shopping ads (create via GMC → Promotions)
priceValidUntil+ compare-at price in JSON-LD for organic and AI agent results
Both systems read sale timing independently — keeping them in sync via the same metafield end date is the cleanest approach.
Flash sale structured data checklist
| Check | Status |
|---|---|
| Sale price is set via compare-at (not automatic discount) | Required for JSON-LD visibility |
priceValidUntil is set from a metafield (not hard-coded) | Prevents stale data after sale ends |
| Metafield is cleared when sale ends | Prevents expired priceValidUntil errors |
availability: LimitedAvailability for low-stock items | Optional but impactful for urgency signals |
| sale price + compare-at price both in JSON-LD for strikethrough | Enables price drop rich results |
| IndexNow ping on sale start and sale end | Ensures crawl of updated prices within hours |
Run a CatalogScan check on your product pages during a sale to verify that priceValidUntil and availability signals are present and correctly formatted in your live JSON-LD output.
Related: Out of stock structured data · Shopify B2C wholesale pricing · Structured data rich snippets guide
Frequently asked questions
Does Shopify automatically update priceValidUntil when a sale ends?
No. Shopify themes don't output priceValidUntil at all by default. If you add it from a metafield, you must clear that metafield when the sale ends — or the schema will show an expired price, which can suppress your product from sale queries.
What's the difference between automatic discounts and compare-at price for structured data?
Compare-at price is reflected in product.price and can go into JSON-LD. Automatic discounts apply only at checkout and cannot be expressed in server-rendered structured data. Only compare-at pricing is AI-agent readable.
How do AI shopping agents handle time-limited sale prices?
If priceValidUntil is in the future, agents surface the sale price. If it's missing, agents trust the current price. If it's in the past, agents may suppress the product from sale queries entirely.
Can I use schema.org to signal flash sale urgency?
Use priceValidUntil for price expiry and LimitedAvailability for low stock. There is no schema property for countdown timers — combine structured data with visible page text for maximum urgency signaling.