Product schema for AI shopping, with copy-paste JSON-LD
Structured data is a small block of code that labels the facts on your product page: this is the name, this is the price, this is in stock, this is the barcode. Search engines have used it for years. AI shopping assistants use the same labels to read your products quickly and accurately.
What "Product schema" means
schema.org/Product is a shared vocabulary for describing products. You add it to a page as JSON-LD, a <script type="application/ld+json"> block that visitors don't see. A crawler that reads the page gets clean facts instead of guessing from your layout.
The fields that matter, in priority order
| Field | Why AI shopping tools care |
|---|---|
name, description, image | The basics of what the product is and how it looks. |
offers.price + priceCurrency | Needed to answer "under $100" questions and compare prices. |
offers.availability | Assistants avoid recommending items they can't confirm are in stock. |
brand | Lets the AI answer brand questions and trust the listing. |
gtin (or gtin12/gtin13), mpn, sku | Unique identifiers that match your item to the same product across the web. See our GTIN guide. |
aggregateRating, review | "Best rated" is a common way to shop. |
shippingDetails, hasMerchantReturnPolicy | Answers "free shipping?" and "easy returns?" questions. |
color, size, material, category | Helps match specific requests like "black, size 10, leather". |
Copy-paste JSON-LD (plain HTML)
Replace the values with your own. Every value must match what's visible on the page.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Men's Merino Wool Crew Sock, Cushioned",
"description": "Midweight merino wool sock with a cushioned sole. Machine washable. Fits US men's sizes 8-12.",
"image": [
"https://yourstore.com/images/sock-front.jpg",
"https://yourstore.com/images/sock-side.jpg"
],
"sku": "MW-CREW-GRY-L",
"gtin13": "0123456789012",
"brand": { "@type": "Brand", "name": "Your Brand" },
"color": "Gray",
"material": "Merino wool",
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "4.7",
"reviewCount": "128"
},
"offers": {
"@type": "Offer",
"url": "https://yourstore.com/products/merino-crew-sock",
"price": "24.00",
"priceCurrency": "USD",
"availability": "https://schema.org/InStock",
"itemCondition": "https://schema.org/NewCondition",
"shippingDetails": {
"@type": "OfferShippingDetails",
"shippingRate": { "@type": "MonetaryAmount", "value": "0", "currency": "USD" },
"shippingDestination": { "@type": "DefinedRegion", "addressCountry": "US" },
"deliveryTime": {
"@type": "ShippingDeliveryTime",
"handlingTime": { "@type": "QuantitativeValue", "minValue": 0, "maxValue": 1, "unitCode": "DAY" },
"transitTime": { "@type": "QuantitativeValue", "minValue": 2, "maxValue": 5, "unitCode": "DAY" }
}
},
"hasMerchantReturnPolicy": {
"@type": "MerchantReturnPolicy",
"applicableCountry": "US",
"returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
"merchantReturnDays": 30,
"returnMethod": "https://schema.org/ReturnByMail",
"returnFees": "https://schema.org/FreeReturn"
}
}
}
</script>
Only include aggregateRating if you display genuine customer reviews on the page.
Copy-paste JSON-LD for Shopify (Liquid)
First, check whether your theme already outputs Product schema (search your theme code for application/ld+json or structured_data). Many themes use Shopify's built-in {{ product | structured_data }} filter. If yours has nothing, or you want more fields, add a snippet like this to your product section or main-product template. Back up your theme first.
{%- assign v = product.selected_or_first_available_variant -%}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": {{ product.title | json }},
"description": {{ product.description | strip_html | strip_newlines | truncate: 5000 | json }},
"url": {{ request.origin | append: product.url | json }},
"image": [{% for img in product.images limit: 5 %}{{ img | image_url: width: 1200 | prepend: "https:" | json }}{% unless forloop.last %},{% endunless %}{% endfor %}],
"brand": { "@type": "Brand", "name": {{ product.vendor | json }} },
{%- if v.sku != blank %}"sku": {{ v.sku | json }},{% endif %}
{%- if v.barcode != blank %}"gtin": {{ v.barcode | json }},{% endif %}
"offers": [
{%- for variant in product.variants -%}
{
"@type": "Offer",
"name": {{ variant.title | json }},
{%- if variant.sku != blank %}"sku": {{ variant.sku | json }},{% endif %}
{%- if variant.barcode != blank %}"gtin": {{ variant.barcode | json }},{% endif %}
"price": {{ variant.price | divided_by: 100.0 | json }},
"priceCurrency": {{ cart.currency.iso_code | json }},
"availability": "https://schema.org/{% if variant.available %}InStock{% else %}OutOfStock{% endif %}",
"url": {{ request.origin | append: variant.url | json }}
}{% unless forloop.last %},{% endunless %}
{%- endfor -%}
]
}
</script>
Notes on this snippet:
gtinuses the variant's Barcode field, so fill those in. If your barcodes aren't real GTINs (for example, internal codes), usempninstead or leave them out.- If a review app already adds rating markup, keep that and don't duplicate it. If it adds its own complete Product block, use one or the other, not both.
- Shipping and return policy schema can be added in the same block or at the organization level. Make sure it matches your real policy.
Organization schema for your homepage
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Your Brand",
"url": "https://yourstore.com",
"logo": "https://yourstore.com/logo.png",
"sameAs": [
"https://www.instagram.com/yourbrand",
"https://www.tiktok.com/@yourbrand"
],
"contactPoint": { "@type": "ContactPoint", "contactType": "customer service", "email": "help@yourstore.com" }
}
</script>
How to test
- Run a product URL through Google's Rich Results Test or the Schema Markup Validator to catch syntax errors.
- Run a ProductSeen scan to see what share of your sampled products have price, availability, brand, GTIN and reviews.
- View the page source (not the browser inspector) to confirm the block is in the raw HTML. Crawlers that don't run JavaScript won't see schema that's injected later.
Mistakes to avoid
- Prices or stock in the schema that don't match the page.
- Marking up reviews that aren't shown on the page or aren't from customers.
- Invalid JSON (a single missing comma breaks the whole block).
- Schema that only appears after JavaScript runs.
Next: give AI assistants a plain-text overview of your store with llms.txt.
See what AI can read about your products
Free scan: AI crawler access, Product schema, GTINs, reviews, sitemap and llms.txt. Results in under a minute, with the top fixes in plain English.
Scan my store freeFrequently asked questions
Does my Shopify theme already include Product schema?
Most current Shopify themes output Product JSON-LD, but many leave out brand, barcodes, reviews, shipping or return details. Older, custom and headless storefronts often have none. Test a product URL to know for sure.
Should I use JSON-LD or microdata?
Both are valid schema.org formats, but JSON-LD is easier to maintain because it sits in one script block instead of being woven through your HTML. Google recommends JSON-LD.
Can I add review markup for reviews I wrote myself?
No. Only mark up genuine reviews from customers that are visible on the page. Fake or self-written review markup violates Google's guidelines and can mislead shoppers.
Will having two Product schema blocks cause problems?
Duplicate or conflicting blocks can confuse parsers. If an app adds its own Product schema, remove or merge the theme's version so there's one complete, consistent block.