Home · The 15 signals · Offers availability
Shopify offers.availability for AI shopping agents
An AI shopping agent will not surface a sold-out product. That's not a ranking heuristic — it's an existential trust rule. If ChatGPT recommends a product the user clicks through to buy and finds out-of-stock, ChatGPT loses that conversation; the user goes back to Google. So every AI agent that retrieves your catalog reads exactly one field to decide whether to even put you in the candidate set: offers.availability on your Product JSON-LD. The field has a strict shape — a schema.org vocabulary URL like https://schema.org/InStock — and most Shopify themes ship the wrong shape. The result is a 6-point hole in the score and, more importantly, a per-product surface where the agent has to guess. Agents that have to guess down-rank.
availability property on the offers node of your Product JSON-LD. Full credit (6 pts) for the absolute schema.org URL form (https://schema.org/InStock or https://schema.org/OutOfStock). Half credit (3 pts) for the bare-name form (InStock / OutOfStock). Zero if missing, malformed, hardcoded to true/false, mapped to a non-schema vocabulary, or set to a value that doesn't update with actual inventory state.
What it is
The offers node on a schema.org Product represents a buy-able offer for that product — price, currency, condition, availability, who is selling it. The availability property carries a value from a fixed schema.org enumeration:
| Value | Meaning | When to use |
|---|---|---|
https://schema.org/InStock | Available right now | Default state, inventory available |
https://schema.org/OutOfStock | Sold out | Zero inventory, no restock date |
https://schema.org/PreOrder | Order now, ships later | Pre-launch products before in-warehouse date |
https://schema.org/BackOrder | Out, restocking | Sold out but reorder is in flight; user can buy and wait |
https://schema.org/Discontinued | No longer made | End-of-life products you keep up for SEO |
https://schema.org/LimitedAvailability | Low stock | Optional — most stores skip this and use InStock |
The "absolute URL" full-credit form is what schema.org documents and what every Google Rich Results Test example shows. Strict JSON-LD parsers — including the ones AI shopping agents run at retrieval time — treat the URL form as a stable, vocabulary-anchored value. The bare-name form ("availability": "InStock") is interpreted as a string rather than a vocabulary URI, and many strict parsers either reject it outright or downgrade their confidence in the offer node. Either way: half credit at best, half a point of trust at worst.
What you want
"offers": {
"@type": "Offer",
"price": "129.00",
"priceCurrency": "USD",
"availability":
"https://schema.org/InStock",
"url": "https://store.com/products/foo"
}
What most themes ship
"offers": {
"@type": "Offer",
"price": "129.00",
"priceCurrency": "USD",
"availability": "InStock",
"url": "https://store.com/products/foo"
}
What page-builder apps inject
"offers": {
"@type": "Offer",
"price": "129.00",
"priceCurrency": "USD",
"availability": true
}
What we still find
"offers": {
"@type": "Offer",
"price": "129.00",
"priceCurrency": "USD",
"availability":
"https://schema.org/InStock"
}
// ...but the variant is sold out
The fourth shape — a hardcoded InStock on a sold-out product — is technically the URL form (full-credit on the static parse) but fails the harder audit: agents that compare the JSON-LD claim against the live page state catch the lie and demote the store on the next crawl. In our launch scan we flag this as a separate alert ("availability emits InStock but rendered HTML shows Sold Out") even when the static signal scores 6/6.
Why AI shopping agents care
- Trust gate, not a ranking factor. An agent that recommends a sold-out product wastes the user's click and burns trust in the agent itself. Most AI shopping retrieval pipelines apply
availability != OutOfStockas a hard filter before ranking. A missing or unparseable availability field gets treated conservatively — usually as "unknown, exclude" — which is functionally the same as "out of stock" for retrieval purposes. - Parseable form keeps you in the candidate set. The schema.org URL form is what every major retrieval pipeline indexes deterministically. Bare-name strings work for Google's tolerant parser but fail strict ones; we have observed it bouncing between half-credit and zero on Bing's Copilot retrieval and on Perplexity's product-card extractor over the past 12 months.
- State-aware downstream signals. A truthful availability field lets the agent surface low-stock urgency, restock dates, and pre-order language correctly. A hardcoded
InStockdoesn't just lie; it costs you the marketing surface where "back in stock" or "ships in 3 weeks" can drive a purchase the agent would otherwise skip. - Cross-channel consistency. Google Merchant Center, Shopify Global Catalog, and Meta product feeds all consume the same
availabilityfield via different ingestion paths. Get this one wrong and the bad shape replicates everywhere.
How to test it on your store
Open any PDP and view source. Find the <script type="application/ld+json"> block whose body contains "@type": "Product". Inside it, find the offers node, then the availability field.
Three things to verify:
- The value is the absolute schema.org URL form — starts with
https://schema.org/. The bare name ("InStock") is half-credit. Anything else is zero. - It tracks live inventory. Open one PDP for a product you know is in stock, one PDP for a product you know is sold out. Verify the JSON-LD reflects the correct state on each. Themes that wire
availabilityto{% raw %}{% if product.available %}{% endraw %}get this for free; themes that hardcodedInStockin a snippet do not. - It validates in the Rich Results Test. Paste the PDP URL into Google's Rich Results Test. If you see a "Missing field 'availability'" or "Invalid value for field 'availability'" warning, you have a parseable problem.
How to fix it
Dawn 14+ emits the URL form by default in snippets/structured-data-product.liquid (or, in newer Dawn versions, inline in sections/main-product.liquid). View source on a PDP and confirm "availability": "https://schema.org/InStock". If you see Dawn but with the bare-name form, you are probably on Dawn 12 or earlier — upgrade or apply the patch below.
Find your theme's product JSON-LD block (search for "@type": "Product" in your theme files — usually in a snippet under snippets/ or inline in your main product section). Replace the availability emission with this exact line:
"availability": "https://schema.org/{% raw %}{% if product.selected_or_first_available_variant.available %}InStock{% else %}OutOfStock{% endif %}{% endraw %}",
Two things to notice. First: the selected_or_first_available_variant.available Liquid call is variant-aware — it switches as the customer toggles between sizes/colors, which is what an agent observing one PDP-load expects to see. Second: the URL is built by string concatenation in Liquid, which means you cannot break it across lines — keep it on one line in the source.
In your app/routes/products.$handle.tsx loader, use the Storefront API availableForSale field on the product (or per-variant selectedVariant.availableForSale if you respect variant state). Map directly: availability: data.product.availableForSale ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock'. Render via the getSeoMeta helper or a <script type="application/ld+json"> tag in the route's component.
Shopify's product model has no first-class "this is a pre-order" flag. The convention: a product tag (preorder, backorder) or a metafield (custom.availability_state). Branch the Liquid: {% raw %}{% if product.tags contains 'preorder' %}https://schema.org/PreOrder{% elsif product.metafields.custom.discontinued %}https://schema.org/Discontinued{% elsif product.available %}https://schema.org/InStock{% else %}https://schema.org/OutOfStock{% endif %}{% endraw %}. The same metafield/tag should drive your "Pre-order" badge in the UI so the JSON-LD and the rendered page never disagree.
5 mistakes themes keep shipping
1. Bare-name form shipped because copy-pasted from a 2014 schema.org tutorial
Schema.org's earliest examples used the bare-name form. Tutorials from the era still rank for "schema.org availability example" queries. Themes built by reading those tutorials emit "availability": "InStock". It validates in lenient parsers (Google's Rich Results Test will accept it with a soft warning) which is why it never gets caught — but strict parsers, including AI agent retrieval pipelines, treat it as an unrecognised value. Half credit on the score; ambiguous value at retrieval.
2. Hardcoded InStock on every PDP regardless of variant state
Page-builder apps and old custom themes sometimes emit a static "availability": "https://schema.org/InStock" string with no Liquid logic — a developer who didn't know about product.available shipped it as "the JSON-LD example I found" and never came back. Sold-out products keep claiming InStock in the structured data. Static parse: full credit. Live audit: agent learns to distrust the store. Use the Liquid conditional above.
3. "availability": true or "available": "yes"
The availability property must be a string referencing the schema.org enumeration. A boolean true/false, the literal string "yes"/"no", or a custom enum like "available"/"unavailable" all parse out as zero. We see this most often in JSON-LD blocks injected by review-widget apps that built their own product schema instead of inheriting Shopify's.
4. Variant-blind: PDP says InStock but the loaded variant is sold out
The Liquid product.available returns true if any variant has stock — even if the currently-selected variant is sold out. AI agents that fetch a PDP with a specific variant ID in the URL get told the product is in stock, click through, and find a "Sold out — choose another size" button. Use product.selected_or_first_available_variant.available instead to track the variant the agent is actually fetching.
5. Discontinued products marked OutOfStock instead of Discontinued
End-of-life products (a colorway you no longer make, a SKU rolled into a successor product) marked OutOfStock tell agents to keep checking back. Marked Discontinued, agents stop indexing them and reroute follow-up "is the X back yet?" queries to your successor product. The fix is a discontinued tag or metafield + the conditional Liquid in the third fix block above.
See also
- The 15 signals — full reference
- Product JSON-LD on PDPs (the parent block
offers.availabilitylives inside) - Structured data validity (the validity check that catches
availabilitytypos as part of the broader parse) - AggregateRating (the second offer-related ranking signal — review-app injection patterns overlap)
- The full 18-signal Agentic Storefronts checklist
- Leaderboard: 100 DTC stores scored on offers.availability and 14 other signals
Is your offers.availability the URL form?
Free 2-minute scan. We parse your PDP JSON-LD, check the availability shape against the schema.org vocabulary, and flag any hardcoded or bare-name values before they cost you a placement.