Optimization Guide

Shopify Sale Event & Promotional Offer Schema for AI Shopping Agents

AI shopping agents answer "best deals on ergonomic chairs right now" and "is the X brand summer sale still on?" by reading temporal Offer signals — sale price, original price, and expiry date — from structured data. Shopify's default schema outputs only the current price with no promotional context, making your sales invisible to deal-hunting AI queries.

TL;DR Add priceValidUntil to sale Offers, use priceSpecification with a ListPrice PriceSpecification for the compare-at price, and add a SaleEvent block to sale landing pages with startDate and endDate. Never show a different sale price in JSON-LD than on the product page.

The Sale Visibility Problem

During a Shopify sale — Black Friday, summer clearance, flash deals — you may discount 20–50% of your catalog. But Shopify's default product JSON-LD doesn't know this is a sale. It outputs the current (sale) price as a static price with no compare-at, no end date, and no promotional tag. AI shopping agents have no way to distinguish your "40% off, ends Sunday" offer from your everyday regular price.

The practical cost: shoppers asking ChatGPT "where can I find the best deal on a standing desk this week?" or Perplexity "which stores have active sales on hiking boots?" cannot be matched to your sale because AI agents lack the three minimum signals — current sale price, original list price, and sale expiry — that identify an active promotion.

Promotion types and the schema signals they need

Promotion typeRequired schema signalsOptional enhancements
Sitewide % off sale priceValidUntil + ListPrice in priceSpecification SaleEvent on homepage with event dates
Flash sale (hours) priceValidUntil with datetime to the hour, availabilityEnds inventoryLevel if truly limited stock
Seasonal clearance priceValidUntil, ListPrice, sale-specific description SaleEvent with "clearance" in name
Volume/bulk discount eligibleQuantity QuantitativeValue (minValue), price at discount Multiple Offer objects per quantity tier
Member-only sale eligibleCustomerType set to member/subscriber segment Pair with member product schema (separate guide)

Temporal Sale Offer Schema

Add priceValidUntil and a dual-price priceSpecification to the Offer block for sale products:

{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Apex Pro Standing Desk",
  "offers": {
    "@type": "Offer",
    "price": "449.00",
    "priceCurrency": "USD",
    "priceValidUntil": "2026-06-30T23:59:00-05:00",
    "availability": "https://schema.org/InStock",
    "priceSpecification": [
      {
        "@type": "UnitPriceSpecification",
        "price": "449.00",
        "priceCurrency": "USD",
        "validThrough": "2026-06-30T23:59:00-05:00",
        "name": "Summer sale price"
      },
      {
        "@type": "UnitPriceSpecification",
        "price": "599.00",
        "priceCurrency": "USD",
        "priceType": "https://schema.org/ListPrice",
        "name": "Regular price"
      }
    ]
  }
}

SaleEvent block for sale landing pages

Add a SaleEvent block to your sale collection page or homepage during promotional periods:

{
  "@context": "https://schema.org",
  "@type": "SaleEvent",
  "name": "CatalogScan Summer Sale 2026",
  "description": "Up to 40% off standing desks, ergonomic chairs, and monitor arms.
    Free shipping on orders over $75.",
  "startDate": "2026-06-01",
  "endDate": "2026-06-30",
  "url": "https://yourdomain.com/collections/summer-sale",
  "location": {
    "@type": "VirtualLocation",
    "url": "https://yourdomain.com/collections/summer-sale"
  },
  "organizer": {
    "@type": "Organization",
    "name": "Your Brand",
    "url": "https://yourdomain.com"
  }
}

Shopify Liquid Implementation

To dynamically inject sale pricing signals only when a product is on sale (compare-at price is set in Shopify admin):

{% if product.compare_at_price_max > product.price_max %}
  {% assign sale_end = settings.current_sale_end_date %}
  "priceValidUntil": {{ sale_end | json }},
  "priceSpecification": [
    {
      "@type": "UnitPriceSpecification",
      "price": {{ product.price | money_without_currency | json }},
      "priceCurrency": {{ cart.currency.iso_code | json }},
      "validThrough": {{ sale_end | json }}
    },
    {
      "@type": "UnitPriceSpecification",
      "price": {{ product.compare_at_price | money_without_currency | json }},
      "priceCurrency": {{ cart.currency.iso_code | json }},
      "priceType": "https://schema.org/ListPrice"
    }
  ]
{% endif %}

Store the sale end date in a theme setting (settings.current_sale_end_date) so it can be updated from the Shopify admin without code changes. Remove it after the sale ends to avoid stale priceValidUntil dates in structured data — expired sale signals are worse than no signals.

Common Mistakes

Fails — misleads AI agents

Showing "price": "49.99" in JSON-LD when the page shows $79.99 with a coupon required

Leaving priceValidUntil: "2026-01-15" active on June 10 — expired date signals an old sale

Correct — consistent signals

Only adding priceValidUntil when the sale price is the page price (automatic discount, no coupon)

Removing all sale schema properties after campaign ends — no stale dates in production

Schema Checklist

FAQ

Does Google display sale prices as rich results in search?

Yes. Google's product rich results can show the sale price alongside a strikethrough regular price in SERPs when both the sale price and ListPrice PriceSpecification are present. This increases SERP click-through rate — users can see the discount before clicking. Google Shopping (free listings) also ingests the compare-at price for badge display ("30% off" labels). This requires schema data to be consistent with what's on the product page.

How do AI agents identify an 'active sale' versus a stale compare-at price?

Via priceValidUntil. Without this property, AI agents cannot tell if the sale price is today's flash deal or a compare-at price that has been "on sale" for 18 months. Adding priceValidUntil with a real expiry date anchors the promotion in time. AI agents that rank results by promotional freshness will surface products with current, timestamped sale signals over products with generic compare-at prices and no expiry context.

Audit Your Sale Schema Coverage

CatalogScan detects products with Shopify compare-at prices that are missing priceValidUntil and ListPrice structured data. Find the gaps in your promotional schema before your next sale season.

Scan your store free