What Offer schema is
Per Schema.org v30.0, Offer is 'an offer to transfer some rights to an item or to provide a service.' The default business function is selling. On a Shopify PDP, Offer is the JSON-LD sub-object inside Product that describes the transaction itself — price, priceCurrency, availability, itemCondition, url, seller, and the sub-objects shippingDetails (OfferShippingDetails) and hasMerchantReturnPolicy (MerchantReturnPolicy).
What Shopify themes emit
The Offer sub-object inside Shopify's auto-emitted Product block typically ships with @type Offer, price (product.price divided by 100), priceCurrency (shop.currency), availability (mapped to https://schema.org/InStock or OutOfStock based on inventory), and url (the canonical product URL). Some themes also emit sku and itemCondition. What Shopify themes do NOT reliably emit on the Offer: priceValidUntil (the sale-end date), seller (the merchant Organization), hasMerchantReturnPolicy, shippingDetails, eligibleRegion.
Offer fields and what they map to on Shopify
Per Schema.org v30.0, the Offer properties Shopify merchants care about are: price (Number/Text — Shopify's product.price divided by 100), priceCurrency (Text, ISO 4217 — shop.currency), priceValidUntil (Date — for sale prices, when the sale ends), availability (ItemAvailability enumeration), itemCondition (OfferItemCondition — typically NewCondition), url (the PDP), seller (Organization — the merchant), sku (Text), gtin (Text — Shopify's product.barcode), shippingDetails (OfferShippingDetails), hasMerchantReturnPolicy (MerchantReturnPolicy).
The availability enumeration
Schema.org defines a fixed set of availability values: InStock, OutOfStock, PreOrder, BackOrder, SoldOut, Discontinued, LimitedAvailability. On Shopify, the mapping from inventory state to availability is straightforward but worth getting right. product.available = true with inventory > 0 maps to InStock. product.available = false maps to OutOfStock. PreOrder applies when product.available = true but the product ships in the future. BackOrder applies when product.available = true but inventory_quantity ≤ 0 with continue selling out of stock enabled.
priceValidUntil and Shopify sales
priceValidUntil is the Date after which the current price is no longer valid. Google's Product documentation strongly recommends it whenever a sale price is shown. On Shopify, the cleanest pattern is a product metafield (e.g. custom.sale_ends_at) that the merchant sets when configuring the sale. The Liquid block then emits priceValidUntil only when the metafield is set, avoiding stale or incorrect dates.
JSON-LD example — Offer with all the fields
The block below is the Offer sub-object inside a complementary Product block on a Shopify PDP. It uses Liquid variables for everything dynamic and includes priceValidUntil only when the sale-end metafield is present.
Validation
Run the live PDP through Google's Rich Results Test. Expected: Product detected, the embedded Offer parsed, zero errors. Common warnings: 'Either offers.priceValidUntil or offers.itemCondition is recommended' (clears when you add either), 'Missing field hasMerchantReturnPolicy' (clears with the dedicated MerchantReturnPolicy block), 'Missing field shippingDetails' (clears with the dedicated OfferShippingDetails block). All three are warnings, not errors — they affect Merchant Center eligibility but not basic rich snippet eligibility.
Shopify gotchas on Offer
Three gotchas catch most Shopify Offer schema work. First: emitting price in cents (12500) instead of dollars (125.00) — Shopify stores prices in cents, the divided_by filter is mandatory. Second: hard-coding priceCurrency instead of using shop.currency, which breaks immediately on stores that switch base currency. Third: hard-coding priceValidUntil to a far-future date on every product, which validates but is meaningless — branch on a real metafield.