Fixes the "no_product_schema" gap

Add schema.org Product JSON-LD to Shopify so AI shopping agents can read & recommend your products

AI shopping agents (and Google) can only recommend a product if they can parse it. Plain HTML is ambiguous; structured data is not. This guide gives you one copy-paste Liquid block that emits a valid Product + Offer object on every product page — name, price, currency, availability, brand, SKU, and image.

Time: about 5 minutes. No apps, no theme rebuild. Works on any Shopify theme that uses standard product objects (Dawn and most OS 2.0 themes).

Why this matters

When an agent or crawler hits your product page, it looks for a <script type="application/ld+json"> block. That JSON tells it, unambiguously: this is a product, here is its price, it is in stock, here is the buy URL. Without it, the agent has to guess from your HTML — and most of them simply skip products they can't confidently parse. The block below removes the guesswork.

Step 1 — Open your product template

  1. In your Shopify admin, go to Online Store → Themes.
  2. On your live theme, click ⋯ → Edit code.
  3. Under Sections, open main-product.liquid (Dawn / OS 2.0). On older themes, open templates/product.liquid instead.

If you'd rather keep it tidy, create snippets/product-jsonld.liquid, paste the block there, and add {% render 'product-jsonld' %} inside the product section. Either location works.

Step 2 — Paste this block

Paste it anywhere inside the product section/template (the bottom of the file is fine). It must render on the product page, where the product object exists.

{%- comment -%}
  schema.org Product + Offer JSON-LD for AI shopping agents.
  Renders on the product page. Uses the currently selected (or first
  available) variant for price, SKU, GTIN and availability.
{%- endcomment -%}
{%- assign variant = product.selected_or_first_available_variant -%}
{%- if variant.available -%}
  {%- assign availability = 'https://schema.org/InStock' -%}
{%- else -%}
  {%- assign availability = 'https://schema.org/OutOfStock' -%}
{%- endif -%}
<script type="application/ld+json">
{
  "@context": "https://schema.org/",
  "@type": "Product",
  "name": {{ product.title | json }},
  "description": {{ product.description | strip_html | truncate: 5000 | json }},
  "image": [
    {%- if product.featured_image -%}
      "https:{{ product.featured_image | image_url: width: 1200 }}"
      {%- for img in product.images limit: 5 -%}
        {%- unless img == product.featured_image -%}
          ,"https:{{ img | image_url: width: 1200 }}"
        {%- endunless -%}
      {%- endfor -%}
    {%- endif -%}
  ],
  "sku": {{ variant.sku | json }},
  {%- if variant.barcode and variant.barcode != blank %}
  "gtin": {{ variant.barcode | json }},
  {%- endif %}
  "brand": {
    "@type": "Brand",
    "name": {{ product.vendor | json }}
  },
  "offers": {
    "@type": "Offer",
    "url": "{{ shop.url }}{{ product.url }}?variant={{ variant.id }}",
    "priceCurrency": {{ cart.currency.iso_code | json }},
    "price": "{{ variant.price | divided_by: 100.0 }}",
    "availability": "{{ availability }}",
    "itemCondition": "https://schema.org/NewCondition"
  }
}
</script>
Why variant.price | divided_by: 100.0 instead of a money filter? Shopify stores prices in cents (an integer). Dividing by 100.0 yields a clean decimal like 24.99 with a dot separator — exactly what schema.org wants. The money_without_currency filter follows your store's display format, which in some locales inserts a thousands separator (e.g. 1,299.00) that makes the JSON price invalid. The cents math is locale-proof.

Step 3 — What each field does

FieldWhat it is
nameProduct title. | json safely quotes and escapes it.
descriptionPlain-text description. strip_html removes markup; truncate keeps it sane; json escapes quotes and newlines.
imageArray of absolute CDN URLs. image_url returns a protocol-relative URL (starts with //), so we prepend https:. Featured image first, then up to 5 more.
skuThe selected variant's SKU. Agents use it to dedupe and match across sources.
gtinVariant barcode (UPC/EAN/ISBN). Only emitted when present. Strongly boosts eligibility in Google Shopping and agent matching.
brandYour store's product.vendor as a Brand object.
offers.urlCanonical buy URL including the variant id, so the agent links to the exact item.
offers.priceCurrencyISO 4217 code (e.g. USD) from the active cart currency — correct even in multi-currency stores.
offers.priceDecimal price from variant cents.
offers.availabilityInStock or OutOfStock, driven by variant.available.
A note on GTIN & availability. gtin is optional but high-value: if your variants have barcodes filled in (Admin → Products → variant → Barcode), the block emits it automatically and agents will trust your listing far more. Leave the barcode empty and the field is simply skipped — still valid. For availability, this block reflects whether the variant can be purchased right now; if you sell out, the JSON flips to OutOfStock on the next page load, so agents stop recommending an item you can't ship.

Step 4 — Validate it

Save the file, open any product page, and check it two ways:

  1. Paste the product URL into the Google Rich Results Test. You want a green “Product snippets” result with no errors. (A “missing field review/aggregateRatingwarning is fine — those are optional.)
  2. Paste the same URL (or the rendered JSON) into the schema.org validator to confirm the object is structurally valid.
  3. Quick sanity check: open the page, View source, and search for application/ld+json — your price, currency and availability should all be filled in.
Already have a Product JSON-LD block? Some themes and SEO apps inject one. Two competing blocks can confuse crawlers. Search your theme for "@type": "Product" first — if one already exists and validates cleanly, you may not need this. If it's missing fields (no offers, no availability), replace it with the block above.

Did this close the gap?

Re-run your store through the free Hatchloop checker to confirm no_product_schema is gone — and see what else AI shopping agents look for (feeds, canonical pricing, crawlability).

Run the free Agent Commerce checker →