Home › Blog › Availability states for AI agents
Shopify product availability states for AI shopping agents: the complete guide to InStock, PreOrder, BackOrder, LimitedAvailability, and Discontinued
Availability state is the first filter AI shopping agents apply. Before they recommend a product, they verify it can actually be purchased — and 69% of Shopify stores output InStock for products that are out of stock, on backorder, or not yet released.
In this guide
- Why availability is the first filter, not an afterthought
- The 7 schema.org availability values and when to use each
- Why Shopify's default JSON-LD outputs the wrong state
- The master Liquid snippet for correct availability mapping
- Per-state JSON-LD patterns with full examples
- Multi-location inventory: when availability varies by store
- Availability state and offerCount: why it affects recommendation slots
- 5 common availability schema mistakes
- FAQ
Why availability is the first filter, not an afterthought
AI shopping agents are recommendation engines that stake their credibility on every citation. Recommending a product that turns out to be unavailable damages the agent's trust with users — and agents don't repeat that mistake. ChatGPT Shopping, Perplexity Commerce, and Google AI Mode all apply availability verification before generating recommendations, which means availability state is an entry gate, not a ranking signal.
The filtering sequence typically works like this:
- Crawl: the agent's crawler reads your Product JSON-LD and stores the
availabilityvalue from each Offer - Filter: when a user query triggers a product lookup, the agent filters to purchasable states:
InStock,BackOrder,LimitedAvailability,PreOrder(if the query allows for future delivery) - Rank: within purchasable products, agents rank by relevance, price, trust signals, and review quality
- Cite: only products that passed the availability filter appear in recommendations
OutOfStock and Discontinued products are filtered out at step 2. If your in-stock products output InStock but your backordered or pre-order products also output InStock, you're not being excluded — but you're corrupting the signal. When an agent crawls your store and later discovers the "InStock" product wasn't available, that mismatch erodes confidence in your catalog signal quality overall.
The 7 schema.org availability values and when to use each
| Schema.org value | Shopify inventory state | Purchasable by AI agent? | Use when |
|---|---|---|---|
InStock |
policy: deny or continue, qty > 0 | Yes | Product is available and ready to ship immediately |
LimitedAvailability |
policy: deny or continue, qty > 0, qty < threshold | Yes | In stock but low quantity — creates urgency signal for AI agents |
PreOrder |
product tagged pre-order, released in future | Yes (with future delivery) | Product exists, can be ordered now, ships when released |
BackOrder |
policy: continue, qty ≤ 0 | Yes (with delay) | Temporarily OOS, order button still active, ships when restocked |
OutOfStock |
policy: deny, qty ≤ 0 | No | OOS and cannot currently be ordered; order button disabled |
InStoreOnly |
POS-only products not sold online | No (online) | Available for in-store purchase only — BOPIS / POS products |
Discontinued |
archived or permanently OOS | No | Product is permanently unavailable and will not return |
The most consequential distinction is between BackOrder and OutOfStock. BackOrder means "you can buy this now, it will ship when stock returns." OutOfStock means "you cannot buy this right now." AI agents include BackOrder in purchasable recommendations; OutOfStock is excluded. This is why backordered products on Shopify stores that use "continue selling when out of stock" should output BackOrder, not OutOfStock — and definitely not InStock.
Related deep-dives
Why Shopify's default JSON-LD outputs the wrong state
Shopify's default Product JSON-LD (generated by most themes using Dawn's approach) maps availability using a single condition:
{% if product.available %}
"availability": "https://schema.org/InStock"
{% else %}
"availability": "https://schema.org/OutOfStock"
{% endif %}
product.available is a Shopify Liquid boolean that returns true if any variant can be purchased — which covers three distinct scenarios that require different schema.org values:
| Scenario | product.available |
Shopify default output | Correct schema.org value |
|---|---|---|---|
| In stock, qty > 5 | true | InStock |
InStock ✓ |
| In stock, qty 1–4 (low) | true | InStock |
LimitedAvailability ✗ |
| Continue selling, qty = 0 | true | InStock |
BackOrder ✗ |
| Continue selling, qty < 0 | true | InStock |
BackOrder ✗ |
| Pre-order product, tag: pre-order | true (if continue) | InStock |
PreOrder ✗ |
| OOS, deny selling, qty = 0 | false | OutOfStock |
OutOfStock ✓ |
The root cause is that product.available conflates three distinct purchase states (ready-to-ship, purchasable-with-delay, pre-order) into a single boolean. Shopify's Liquid API has the granular properties needed to distinguish them — the default theme just doesn't use them.
"availability": "InStock" instead of the full URI "availability": "https://schema.org/InStock". AI agents and validators may accept the short form, but Google's Rich Results Test requires the full URI. If you're using a third-party theme, curl your product page and verify: curl -s https://yourstore.com/products/your-product | grep '"availability"'
The master Liquid snippet for correct availability mapping
This snippet reads Shopify's inventory signals at the variant level (not just product.available) and outputs the correct schema.org availability value. Replace the availability line in your sections/main-product.liquid JSON-LD block:
{% comment %}--- Availability mapping ---{% endcomment %}
{% assign av_variant = product.selected_or_first_available_variant %}
{% assign av_url = 'https://schema.org/OutOfStock' %}
{% if product.tags contains 'pre-order' or product.tags contains 'preorder' %}
{% assign av_url = 'https://schema.org/PreOrder' %}
{% elsif product.tags contains 'discontinued' or product.tags contains 'archived' %}
{% assign av_url = 'https://schema.org/Discontinued' %}
{% elsif av_variant != blank %}
{% if av_variant.inventory_management == blank %}
{% comment %}No inventory tracking — assume InStock{% endcomment %}
{% assign av_url = 'https://schema.org/InStock' %}
{% elsif av_variant.inventory_quantity > 0 %}
{% if av_variant.inventory_quantity < 5 %}
{% assign av_url = 'https://schema.org/LimitedAvailability' %}
{% else %}
{% assign av_url = 'https://schema.org/InStock' %}
{% endif %}
{% elsif av_variant.inventory_policy == 'continue' %}
{% comment %}Continue selling when OOS = BackOrder{% endcomment %}
{% assign av_url = 'https://schema.org/BackOrder' %}
{% else %}
{% assign av_url = 'https://schema.org/OutOfStock' %}
{% endif %}
{% endif %}
{% comment %}Use {{ av_url }} in your Offer JSON-LD:{% endcomment %}
{% comment %}"availability": "{{ av_url }}"{% endcomment %}
Key decisions encoded in this snippet:
- Tag-first routing: pre-order and discontinued tags override inventory state, because Shopify inventory for pre-order products is often set to "continue selling" which would otherwise map to BackOrder.
- LimitedAvailability threshold of 5: adjust this to your category norm. For luxury goods, 5 might be the right threshold. For commodity products with 1,000-unit runs, you might set it at 3 or even 2. The goal is to signal genuine scarcity, not create false urgency.
- No inventory management → InStock: products without inventory tracking in Shopify are typically print-on-demand, digital, or always-in-stock items. InStock is the safest default.
Per-state JSON-LD patterns with full examples
Standard in-stock product. No additional availability signals needed — the state alone tells AI agents this product is ready to ship.
{
"@type": "Offer",
"url": "https://yourstore.com/products/your-product",
"priceCurrency": "USD",
"price": "49.00",
"availability": "https://schema.org/InStock",
"itemCondition": "https://schema.org/NewCondition",
"seller": { "@type": "Organization", "name": "Your Store" }
}
Low-inventory scarcity signal. Combine with inventoryLevel to give AI agents a quantity signal. ChatGPT Shopping and Perplexity may surface a "limited stock" label in their output for products using this state — a direct conversion signal to end users.
{
"@type": "Offer",
"url": "https://yourstore.com/products/your-product",
"priceCurrency": "USD",
"price": "49.00",
"availability": "https://schema.org/LimitedAvailability",
"inventoryLevel": {
"@type": "QuantitativeValue",
"value": {{ av_variant.inventory_quantity }},
"unitCode": "EA"
},
"itemCondition": "https://schema.org/NewCondition"
}
Pre-release product available for advance purchase. The critical addition is availabilityStarts (the ship date) — 83% of pre-order products omit this, which means AI agents can't tell users when the product ships. Without a release date, agents are reluctant to recommend pre-orders because they can't answer the follow-up question "when does it arrive?"
{
"@type": "Offer",
"url": "https://yourstore.com/products/your-product",
"priceCurrency": "USD",
"price": "89.00",
"availability": "https://schema.org/PreOrder",
"availabilityStarts": "{{ product.metafields.preorder.ship_date | date: '%Y-%m-%d' }}",
"additionalProperty": [
{
"@type": "PropertyValue",
"name": "preorderDeposit",
"value": "{{ product.metafields.preorder.deposit_amount | money_without_currency }}"
},
{
"@type": "PropertyValue",
"name": "estimatedDelivery",
"value": "{{ product.metafields.preorder.ship_date | date: '%B %Y' }}"
}
],
"itemCondition": "https://schema.org/NewCondition"
}
Required metafields: preorder.ship_date (date), preorder.deposit_amount (number — optional, for deposit-based pre-orders).
Temporarily OOS but still purchasable. BackOrder is the correct value for Shopify's "continue selling when out of stock" inventory policy — the product can be purchased and ships when stock is replenished. Add availabilityStarts if you know the restock date; this significantly increases recommendation rate because agents can include a "ships by [date]" qualifier.
{
"@type": "Offer",
"url": "https://yourstore.com/products/your-product",
"priceCurrency": "USD",
"price": "49.00",
"availability": "https://schema.org/BackOrder",
{% if product.metafields.inventory.restock_date != blank %}
"availabilityStarts": "{{ product.metafields.inventory.restock_date | date: '%Y-%m-%d' }}",
{% endif %}
"additionalProperty": {
"@type": "PropertyValue",
"name": "backorderNote",
"value": "Ships when restocked — order secured at current price"
},
"itemCondition": "https://schema.org/NewCondition"
}
Permanently unavailable. Products marked Discontinued should also have noindex added to the page meta — keeping them indexable with Discontinued availability signals to AI agents that your store stocks products that can't be bought, which dilutes catalog quality signals. For SEO, use a 301 redirect to a related product or the relevant collection.
<!-- In <head> of the product page -->
<meta name="robots" content="noindex,follow">
{
"@type": "Offer",
"url": "https://yourstore.com/products/discontinued-product",
"availability": "https://schema.org/Discontinued",
"additionalProperty": {
"@type": "PropertyValue",
"name": "discontinuedReason",
"value": "Product line ended — see current alternatives"
}
}
Important: don't use Discontinued for seasonal products that will return next season — use OutOfStock with a future availabilityStarts date instead. Discontinued signals permanent end-of-life.
Multi-location inventory: when availability varies by store
Shopify's multi-location inventory is one of the more complex availability scenarios for structured data. When a product is InStock at your Los Angeles warehouse but OutOfStock at your New York warehouse, what does the schema.org availability value say?
The standard approach is to use the aggregate availability: if any location can fulfill the order, the Offer availability is InStock or LimitedAvailability. The location-level detail is expressed through shippingDetails and, for BOPIS (Buy Online Pick Up In Store) scenarios, through availableAtOrFrom pointing to the fulfilling location.
{
"@type": "Offer",
"availability": "https://schema.org/InStock",
"availableAtOrFrom": {
"@type": "Place",
"name": "CatalogScan Store — Los Angeles",
"address": {
"@type": "PostalAddress",
"addressLocality": "Los Angeles",
"addressRegion": "CA",
"addressCountry": "US"
}
},
"shippingDetails": {
"@type": "OfferShippingDetails",
"shippingDestination": {
"@type": "DefinedRegion",
"addressCountry": "US"
},
"deliveryTime": {
"@type": "ShippingDeliveryTime",
"handlingTime": { "@type": "QuantitativeValue", "minValue": 1, "maxValue": 2, "unitCode": "DAY" },
"transitTime": { "@type": "QuantitativeValue", "minValue": 2, "maxValue": 5, "unitCode": "DAY" }
}
}
}
For stores with Shopify POS + online inventory, where some products are in-store only, use InStoreOnly combined with availableAtOrFrom pointing to your physical location. AI shopping agents treat InStoreOnly as a purchasable state for users willing to pick up — Perplexity Commerce and Google AI Mode both surface InStoreOnly products in local product queries.
See also
Availability state and offerCount: why it affects recommendation slots
AI shopping agents don't just read the primary Offer's availability — they assess the offer array's overall quality. A product with a single Offer that's InStock is simpler to recommend than a product with a ProductGroup of 12 variants where 10 are OutOfStock and 2 are InStock. When the ratio of unavailable-to-available offers is high, agents may deprioritize the product in favor of cleaner catalog entries.
The practical implication: for products with high variant counts and mixed availability, include only currently purchasable variants in your primary Offer array. You can structure this in Liquid:
"offers": [
{% for variant in product.variants %}
{% unless forloop.first %},{% endunless %}
{% assign v_av = '' %}
{% if variant.available %}
{% if variant.inventory_quantity < 5 and variant.inventory_management != blank %}
{% assign v_av = 'https://schema.org/LimitedAvailability' %}
{% else %}
{% assign v_av = 'https://schema.org/InStock' %}
{% endif %}
{% elsif variant.inventory_policy == 'continue' %}
{% assign v_av = 'https://schema.org/BackOrder' %}
{% else %}
{% assign v_av = 'https://schema.org/OutOfStock' %}
{% endif %}
{
"@type": "Offer",
"sku": "{{ variant.sku }}",
"name": "{{ variant.title | escape }}",
"price": "{{ variant.price | money_without_currency }}",
"priceCurrency": "{{ shop.currency }}",
"availability": "{{ v_av }}",
"url": "{{ shop.url }}/products/{{ product.handle }}?variant={{ variant.id }}"
}
{% endfor %}
]
This outputs per-variant availability across the entire variant array — giving AI agents a complete picture of which sizes, colors, or configurations are actually available. See the ProductGroup JSON-LD guide for how to wire this into the ProductGroup.hasVariant pattern for maximum variant visibility.
5 common availability schema mistakes
product.available returns true for products with inventory_policy: continue regardless of quantity. The default theme outputs InStock. The correct value is BackOrder (purchasable, ships when restocked). Fix: add an inventory_policy check to your Liquid snippet — see the master snippet above.PreOrder without an availabilityStarts date is the schema equivalent of a pre-order page that says "coming soon" with no estimated date. AI agents won't refuse to recommend it, but they'll deprioritize it against pre-orders with dates — because agents that recommend "coming soon, no ETA" get user complaints. Always pair PreOrder with a ship date, even if it's approximate (month and year is enough).OutOfStock tells AI agents "you cannot buy this product." BackOrder tells them "you can buy this, it ships when restocked." For temporarily OOS products that your store will restock (using Shopify's "continue selling" policy), BackOrder keeps them in AI recommendation consideration. OutOfStock removes them from recommendations entirely until the next crawl. The distinction costs you recommendation slots for every day an item is incorrectly marked OutOfStock.LimitedAvailability for genuinely scarce inventory. For made-to-order products, use PreOrder or custom MTO schema patterns instead.availability: Discontinued but no noindex tag are still crawled and counted against your catalog quality signals. A store with 300 indexed products where 60 are Discontinued looks like a store where 20% of the catalog is permanently unavailable. Add <meta name="robots" content="noindex"> to discontinued product pages and 301-redirect them to their best available alternatives. This improves your crawlable catalog quality ratio across all AI agent recommendation models.FAQ
Does availability schema affect my CatalogScan score?
Yes — CatalogScan's Offer signal cluster includes availability state accuracy as a component. Specifically, the system flags stores where product.available is false but the JSON-LD availability value is InStock, which is the most common mismatch we find. Correcting this directly improves your Offer signal score and prevents AI agent exclusion based on availability verification. Run a free scan to see whether your store has availability mismatches in the signal breakdown.
What is the difference between BackOrder and OutOfStock in schema.org?
OutOfStock means the product cannot be ordered at all right now. BackOrder means the product is temporarily unavailable but can still be ordered — it will ship when stock is replenished. In Shopify terms: OutOfStock maps to inventory_policy: deny + quantity: 0 (order button disabled), while BackOrder maps to inventory_policy: continue + quantity: 0 (order button still active). AI shopping agents include BackOrder in purchasable recommendations; OutOfStock is excluded entirely.
Should I use LimitedAvailability for flash sales and limited editions?
LimitedAvailability is correct for products with genuine low inventory — typically fewer than 5–10 units on fast-moving SKUs. For flash sales, combine LimitedAvailability with priceValidUntil on the Offer to signal the time-limited price. For true limited editions (products that will be discontinued once sold through), use LimitedAvailability while units remain and switch to Discontinued when they’re gone. Don’t use LimitedAvailability on products that are always in stock — it’s a trust signal, and overuse dilutes it.
How does Shopify's "continue selling when out of stock" policy map to schema.org?
When a Shopify product’s inventory policy is set to “continue,” product.available returns true even when inventory_quantity is 0 or negative. Shopify’s default JSON-LD outputs InStock whenever product.available is true — so these products incorrectly show InStock. The correct mapping is: continue selling + quantity ≤ 0 = BackOrder. You need a custom Liquid snippet that checks inventory_policy and inventory_quantity separately, not just product.available. The master snippet in this guide handles this case.
Do AI shopping agents actually verify availability against live inventory, or just read the schema?
AI shopping agents like ChatGPT Shopping and Perplexity read availability from your JSON-LD and product feed at crawl time — they do not make live inventory API calls on every recommendation. This means stale availability schema causes real problems: a product crawled as InStock may actually be OOS when a user tries to purchase. Agents that receive complaint signals about unavailable products they recommended will deprioritize your store. Keep your availability schema accurate and, where possible, use Google Merchant Center feed refreshes (which AI agents inherit) to propagate inventory changes between crawls. See the AI agent attribution guide for how to set up tracking that shows you when availability mismatches are affecting conversion.
Is your store outputting the right availability states?
CatalogScan flags InStock mismatches, missing PreOrder dates, and BackOrder vs. OutOfStock errors as part of the Offer signal audit. 90 seconds, no login.
Run a free scan See all 15 signals