Skip to content
Published Authored byBilly Reiner

Schema · How-to

Shopify Product schema

Shopify themes auto-emit a Product JSON-LD block on every product page1name, image, description, an offers object with price and availability, plus sku and (on most themes) brand mapped from product.vendor. Dawn and Horizon build it with Shopify's structured_data filter, which prints a Product for a product without variants and a ProductGroup when it has variants14. The 2026 install rule: keep one Product entity per page. To add fields, replace the theme's line in main-product.liquid4 with your own complete block rather than adding a second one. What the theme's output leaves out: itemCondition, hasMerchantReturnPolicy, shippingDetails, and aggregateRating + review (only when real customer reviews exist). gtin is printed only when the variant's Barcode field is filled in15.

Google's Product documentation6 defines two surfaces — Product snippets (pages where people can't buy the product directly, like editorial reviews) and Merchant listings (pages where customers can buy). Shopify PDPs are Merchant listings. Structured data alone can make them eligible; a Google Merchant Center feed is optional, and Google says using both "maximizes your eligibility to experiences." Filling the fields on Shopify's Catalog optimisation list2 (barcode, vendor, variants) feeds both.

What Product schema is

Product is the Schema.org type for a physical or digital good offered for sale. Per Schema.org v30.0, Product properties include name (Text), image (ImageObject or URL), description, brand (Brand or Organization), sku, gtin / gtin8 / gtin12 / gtin13 / gtin14 (Text identifiers), mpn (Manufacturer Part Number), offers (Offer with price + availability), aggregateRating, review, plus variant-related properties isVariantOf and hasVariant for ProductGroup composition. The mental model: Product describes the item itself; Offer describes the transaction. A Shopify PDP carries both.

What's missing from Shopify's auto-emission — itemCondition, AggregateRating from real reviews, hasMerchantReturnPolicy, shippingDetails, and a gtin wherever the Barcode field is empty — is what Google's merchant listing documentation lists as recommended10. Schema.org v30.0 dates from 2026-03-195; the Product type has been stable across the last several vocabulary versions, so the field set you author today will not break next quarter.

When to use Product schema on Shopify

Use Product on every product page on a Shopify store. The schema applies to physical goods (apparel, jewellery, furniture, food), digital goods (downloads, courses), and services sold as packaged products (a one-off coaching package, a fixed-price installation). Do not use Product on collection pages — those are CollectionPage or ItemList. Do not use Product on the homepage. Do not use Product on the cart, search, or checkout routes (which Shopify blocks from indexing by default anyway via robots.txt).

An edge case worth naming: services sold as fixed-price products on Shopify. If the customer takes possession of a digital good (a course they download), use Product. If the customer receives ongoing service delivery, Schema.org's Service type is technically more accurate — but Shopify's product model treats both identically, and Product is the more familiar type to AI shopping engines. For Shopify-sold services, Product is the practical choice unless the page is explicitly framed as ongoing service provision.

What Shopify themes already emit

Dawn (since v15.0.0) and Horizon print the product's JSON-LD with one line, {{ product | structured_data }}. Shopify's filter outputs a Product for a product without variants and a ProductGroup for a product with variants. On the Dawn demo store (read 2026-09-16) a multi-variant product comes out as a ProductGroup with @id /products/[handle]#product, name, description, brand (from product.vendor), productGroupID and url, plus one Product per variant in hasVariant, each with name, sku, image, gtin when the variant's Barcode field is filled, and an Offer with price, priceCurrency, availability and url. What the filter does NOT emit: itemCondition, variesBy, hasMerchantReturnPolicy, shippingDetails, aggregateRating, review. Older and third-party themes differ, so view source before you write anything.

The implication: adding to Product schema on Shopify means editing the one block the page already has, not adding a second one next to it. Two separate descriptions of the same product give Google two Product entities for one page. Either keep the theme's output and put store-wide policies on Organization (see below), or replace the theme's line with a block of your own that carries every field.

Rich-results status in 2026

Product structured data drives two distinct Google surfaces in 2026. Product snippets are for pages where people can't buy the product directly, such as review and comparison pages; they need name plus one of offers, review or aggregateRating. Merchant listings are for pages where customers can buy, which is every Shopify PDP; they need name, image and an Offer with a price above zero and a priceCurrency. Structured data on the page is enough to be eligible for merchant listing experiences. A Merchant Center feed is optional, and Google recommends doing both.

The honest framing: the auto-emitted Shopify Product schema already carries every field Google requires for a merchant listing, as long as the product has an image and a price above zero10. What it lacks are recommended fields: shipping and return details (which Google prefers you declare once for the store on Organization), itemCondition, and ratings from real reviews. Google never guarantees a rich result, so treat the recommended fields as eligibility for richer listings, not a promise of them.

Product schema on Shopify in 2026

2

Google surfaces Product schema feeds: Product snippets (pages where you can't buy) and Merchant listings (pages where you can).

Google Search Central · 2026-09-16
Yes

Shopify themes auto-emit Product schema. View-source on a Dawn PDP confirms name, image, description, sku, offers.

Shopify Help · 2026-05-22
7 fields

Shopify Catalog optimisation doc names seven product fields AI platforms read — Title, Description, Images, Product organization, Barcode, Variants, External URL.

Shopify Help · 2026-05-22

Required and recommended Product fields

For a merchant listing on a Shopify PDP, Google requires name, image and offers, with the Offer carrying price (greater than zero) and priceCurrency. A product snippet needs only name plus one of offers, review or aggregateRating. Recommended for merchant listings: brand, gtin (the most specific of gtin8/gtin12/gtin13/gtin14 that applies), mpn, sku, description, aggregateRating, review, and on the Offer availability, itemCondition, hasMerchantReturnPolicy and shippingDetails. The fields most worth adding because Shopify's structured_data output leaves them out: itemCondition, aggregateRating (when real customer reviews exist), and return and shipping policies.

  • name — required. Auto-emitted by Shopify themes from product.title.
  • image — required for merchant listings. Auto-emitted from the product or variant image. Image URLs must be crawlable and indexable.
  • description — recommended. Auto-emitted from product.description (stripped).
  • brand — recommended. Most Shopify themes emit a Brand object from product.vendor. If yours does not, add it.
  • sku — recommended. Auto-emitted from the variant's SKU. Google rejects whitespace in it.
  • gtin / gtin8 / gtin12 / gtin13 / gtin14 — recommended. Dawn and Horizon print the variant's barcode as gtin when the Barcode field is filled; Google prefers the most specific length property. The GTIN leaf maps it by length.
  • mpn — Manufacturer Part Number. Recommended when gtin is unavailable. Shopify does not have a native MPN field; use a metafield (e.g. custom.mpn) and reference it via Liquid.
  • offers — required for merchant listings. Auto-emitted with price, priceCurrency, availability, url. See the Offer leaf for the full sub-object spec.
  • aggregateRating — recommended. NOT auto-emitted. Add only when real customer reviews are visible on the page. See the AggregateRating leaf.
  • review — recommended. Individual reviews as Review objects.
  • hasMerchantReturnPolicy — recommended on the Offer. NOT auto-emitted. Google prefers one store-wide policy on Organization, with Offer-level policies for exceptions. See the hasMerchantReturnPolicy leaf.
  • shippingDetails — recommended on the Offer. NOT auto-emitted. Same rule: store-wide shipping goes on Organization. See the shippingDetails leaf.
  • productID — optional Schema.org property. Google's product documentation doesn't list it, so it adds nothing for rich results.

Full Liquid-driven JSON-LD example for a Shopify PDP

The block below is the full Product schema for a Shopify-sold ceramic vase with no variants. It carries the fields the theme already prints plus the ones it leaves out (itemCondition, a return policy, shipping details), built from Liquid variables so it re-renders per product. In the theme's main-product.liquid, find the line that prints {{ product | structured_data }} inside a script tag and replace it with this block. Products with variants fall through to the theme's own ProductGroup.

JSON-LD One Product block for a single-variant Shopify PDP. It replaces the theme's structured_data line; products with variants keep the theme's ProductGroup
{%- assign variant = product.selected_or_first_available_variant -%}
{%- if product.has_only_default_variant -%}
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "@id": {{ request.origin | append: product.url | append: '#product' | json }},
  "name": {{ product.title | json }},
  "url": {{ request.origin | append: product.url | json }},
  "description": {{ product.description | strip_html | strip | json }},
  {%- if product.featured_image %}
  "image": {{ product.featured_image | image_url: width: 1200 | prepend: 'https:' | json }},
  {%- endif %}
  {%- if product.vendor != blank %}
  "brand": {
    "@type": "Brand",
    "name": {{ product.vendor | json }}
  },
  {%- endif %}
  {%- if variant.sku != blank %}
  "sku": {{ variant.sku | json }},
  {%- endif %}
  {%- if variant.barcode != blank %}
  "gtin": {{ variant.barcode | json }},
  {%- endif %}
  "offers": {
    "@type": "Offer",
    "url": {{ request.origin | append: product.url | json }},
    "price": {{ variant.price | divided_by: 100.0 }},
    "priceCurrency": {{ cart.currency.iso_code | json }},
    "availability": "{%- if variant.available -%}https://schema.org/InStock{%- else -%}https://schema.org/OutOfStock{%- endif -%}",
    "itemCondition": "https://schema.org/NewCondition",
    "hasMerchantReturnPolicy": {
      "@type": "MerchantReturnPolicy",
      "applicableCountry": "US",
      "returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
      "merchantReturnDays": 30,
      "returnMethod": "https://schema.org/ReturnByMail",
      "returnFees": "https://schema.org/FreeReturn"
    },
    "shippingDetails": {
      "@type": "OfferShippingDetails",
      "shippingRate": {
        "@type": "MonetaryAmount",
        "value": 0,
        "currency": {{ cart.currency.iso_code | json }}
      },
      "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"
        }
      }
    }
  }
}
</script>
{%- else -%}
<script type="application/ld+json">{{ product | structured_data }}</script>
{%- endif -%}

Where to paste it on Shopify

Product schema goes inside main-product.liquid (modern themes) or product-template.liquid (older themes). Open the theme's code editor (Online Store > Themes > Actions > Edit code) or pull the theme down via Shopify CLI. Find the section file for the product template and the script tag that prints the structured_data filter. Replace that tag with the block above. Save. Push if using CLI. View source on the live product page after deployment: a product without variants should show your block and no other Product; a product with variants should still show the theme's ProductGroup.

An alternative pattern for stores already using JSON-LD via a schema app (JSON-LD for SEO, Smart SEO, Avada): pick one source for Product. Either disable the app's Product schema, or remove the theme's structured_data line and let the app own Product entirely. The app-vs-native trade-off is covered on the schema-app-vs-theme leaf; for stores doing the install themselves, the theme-native approach above keeps the markup in main-product.liquid, the section Shopify's own SEO FAQ identifies as generating the product page's H14.

Validation steps

Run the live product page URL through Google's Rich Results Test. The expected output: one Product (or ProductGroup) detected under Merchant listings, zero errors, and warnings only on recommended fields you haven't added, typically hasMerchantReturnPolicy and shippingDetails when you haven't declared them. If you use Merchant Center as well, check there that the feed and the page agree; that check happens in the Merchant Center console, not in the Rich Results Test.

A common validation gotcha on Shopify: the test reports a missing identifier on a product whose Barcode field is empty. Don't fabricate a GTIN to silence the warning. If your products legitimately don't have one (handmade, vintage), leave the field unset. If the Barcode field is filled and no gtin appears in view-source, your theme predates the structured_data filter or builds its own block; add the gtin line from the example.

Common Shopify gotchas

Five gotchas catch most Shopify Product schema work. First: adding a second Product block next to the theme's, which gives Google two descriptions of one product. Second: forgetting to divide the price by 100.0 (Shopify stores prices in the currency's subunit). Third: printing product.title or product.vendor inside quote marks instead of through the | json filter (| escape is for HTML and doesn't make a value JSON-safe), causing JSON syntax errors when titles contain double quotes or line breaks. Fourth: marking up ratings shoppers can't see on the page, or ratings copied from another site; Google's review snippet rules require ratings that are visible and sourced directly from users. Fifth: leaving Product schema in theme.liquid (a site-wide template), where product.* variables are unbound on non-product pages and the schema fails to render.

A sixth gotcha for variant products: a single Product block describes one variant. If you sell a product in five sizes or three colours, Google's variant documentation models it as a ProductGroup with one Product per variant in hasVariant, each with its own ID such as the SKU or GTIN12. Dawn and Horizon already output that shape through the structured_data filter14, which is why the example above only replaces the block for products without variants. Older themes that print a single Product for every product need a ProductGroup block written by hand.

Product references Brand (its own Schema.org type, not an Organization subtype, though brand also accepts an Organization) and contains Offer (the pricing sub-object). When real customer reviews are shown on the page, Review and AggregateRating layer in. BreadcrumbList belongs on every product page. For variant products, ProductGroup is the parent type. The Organization that owns the catalog should also have its own site-wide schema block in theme.liquid.