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.

Last updated 2026-04-30 · Deep signal · 6 pts

6 ptsRanking-spread weight
~35%Of stores ship the short form
1 lineIn product.liquid
What this signal scores: the 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:

ValueMeaningWhen to use
https://schema.org/InStockAvailable right nowDefault state, inventory available
https://schema.org/OutOfStockSold outZero inventory, no restock date
https://schema.org/PreOrderOrder now, ships laterPre-launch products before in-warehouse date
https://schema.org/BackOrderOut, restockingSold out but reorder is in flight; user can buy and wait
https://schema.org/DiscontinuedNo longer madeEnd-of-life products you keep up for SEO
https://schema.org/LimitedAvailabilityLow stockOptional — 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.

Full credit — URL form

What you want

"offers": {
  "@type": "Offer",
  "price": "129.00",
  "priceCurrency": "USD",
  "availability":
    "https://schema.org/InStock",
  "url": "https://store.com/products/foo"
}
Half credit — bare name

What most themes ship

"offers": {
  "@type": "Offer",
  "price": "129.00",
  "priceCurrency": "USD",
  "availability": "InStock",
  "url": "https://store.com/products/foo"
}
Zero — wrong shape

What page-builder apps inject

"offers": {
  "@type": "Offer",
  "price": "129.00",
  "priceCurrency": "USD",
  "availability": true
}
Zero — hardcoded stale

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

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:

  1. 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.
  2. 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 availability to {% raw %}{% if product.available %}{% endraw %} get this for free; themes that hardcoded InStock in a snippet do not.
  3. 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

Default Dawn theme: verify it's already correct2 minfree

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.

Custom theme: one-line Liquid fix5 minfree

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.

Hydrogen / Remix: route loader-time JSON-LD15 minfree

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.

Pre-orders, back-orders, and discontinued products10 minfree

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

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.

Scan my store → See all 15 signals