Skip to content
🇺🇸

Product Schema Example for Shopify Apparel

Copy-ready JSON-LD Product and ProductGroup schema examples for Shopify apparel — covers size and color variants, offers, breadcrumbs, and the AggregateRating rules that decide whether AI search engines actually cite your product page.

5 min read

Apparel product pages on Shopify get cited by AI shopping engines based on how precisely the JSON-LD describes the variant landscape. A shopper asking “linen shirt that runs true to size in medium” on ChatGPT, Perplexity, or Gemini needs the AI to recognise that the specific variant exists, is in stock, has a price, and is described distinctly from the L and XL siblings. Generic Product schema with no variant detail forces the AI to guess; ProductGroup with hasVariant gives it the data to cite confidently.

This example ships two copy-ready JSON-LD blocks: a ProductGroup for the variant matrix, and a BreadcrumbList for category context. Both are calibrated to pass Google Rich Results Test cleanly and to mirror the visible page content (the rule AI engines actually enforce in practice).

When to use Product vs ProductGroup

Use caseSchemaWhy
Single-SKU item (one size, one color)ProductNo variant matrix to express — adding ProductGroup is overkill.
Apparel with size variants onlyProductGroup + variesBy: sizeAI shopping queries are size-conditioned; flat Product loses that.
Apparel with color variants onlyProductGroup + variesBy: colorAI queries are color-conditioned.
Apparel with size × color matrixProductGroup + both variesByCartesian product of variants; each hasVariant is the unique SKU.
Bundle / kit (multiple parents)Product per item + custom isRelatedToProductGroup describes one product’s variants, not multi-product bundles.

Shopify’s default Liquid templates emit Product regardless of variant count. Upgrading to ProductGroup for any apparel store with multiple sizes is a one-time theme edit (or a Shopify App that handles it automatically); the AI-shopping citation lift is worth the effort.

ProductGroup JSON-LD (apparel with variants)

Drop this into a product page template, replacing the placeholder values. The example shows a 2-variant subset; in production you’ll have one hasVariant entry per real SKU.

ProductGroup JSON-LD (apparel variants) json
{
  "@context": "https://schema.org",
  "@type": "ProductGroup",
  "name": "Linen Relaxed Shirt",
  "description": "A breathable linen shirt in three colors, sizes XS–XL. 100% European linen, pre-washed for softness.",
  "brand": {
    "@type": "Brand",
    "name": "Example Apparel"
  },
  "productGroupID": "linen-shirt-001",
  "variesBy": ["https://schema.org/size", "https://schema.org/color"],
  "hasVariant": [
    {
      "@type": "Product",
      "sku": "LINEN-SHIRT-WHITE-M",
      "name": "Linen Relaxed Shirt — White / M",
      "color": "White",
      "size": "M",
      "material": "Linen",
      "image": "https://example.com/products/linen-shirt-white.jpg",
      "offers": {
        "@type": "Offer",
        "url": "https://example.com/products/linen-shirt?variant=white-m",
        "priceCurrency": "USD",
        "price": "79.00",
        "availability": "https://schema.org/InStock",
        "itemCondition": "https://schema.org/NewCondition"
      }
    },
    {
      "@type": "Product",
      "sku": "LINEN-SHIRT-BLACK-S",
      "name": "Linen Relaxed Shirt — Black / S",
      "color": "Black",
      "size": "S",
      "material": "Linen",
      "image": "https://example.com/products/linen-shirt-black.jpg",
      "offers": {
        "@type": "Offer",
        "url": "https://example.com/products/linen-shirt?variant=black-s",
        "priceCurrency": "USD",
        "price": "79.00",
        "availability": "https://schema.org/InStock",
        "itemCondition": "https://schema.org/NewCondition"
      }
    }
  ]
}

Pair the ProductGroup with a BreadcrumbList so AI engines understand the category hierarchy your product lives in. The breadcrumb URLs must match the page’s visible breadcrumb exactly.

BreadcrumbList JSON-LD json
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://example.com/"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Shirts",
      "item": "https://example.com/collections/shirts"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "Linen Relaxed Shirt",
      "item": "https://example.com/products/linen-shirt"
    }
  ]
}

Common errors (in order of how often they trip stores up)

  1. Marking up variants that aren’t visible or selectable on the page. Schema must mirror visible reality. If your product page only shows S / M / L but the JSON-LD declares XS through XXL, AI engines will eventually try to cite a variant that 404s and downweight the store.

  2. Using AggregateRating when no review widget exists. Either show the reviews visibly (use a Shopify Reviews app) or drop the schema field. Don’t fabricate.

  3. Inconsistent currency, price, or availability between schema and the visible product page. Buyer sees $79 but schema declares 89.00 — AI engines pick the schema number, cite it, and the buyer feels misled. Worse, Google flags the schema as untrustworthy.

  4. Reusing the same sku across variants. Shopify auto-generates unique SKUs but merchants often override them. Duplicate SKUs in hasVariant cause AI engines to merge variants incorrectly.

  5. Mixing ProductGroup and Product such that variant-specific names get lost. If you emit both Product and ProductGroup for the same item, make sure the Product instance is the parent (no variant detail) and ProductGroup.hasVariant contains the variants. Don’t emit Product for each variant outside the group.

  6. Adding medical or unsupported claims to apparel descriptions. “Posture-correcting” / “improves circulation” / “anti-bacterial fabric prevents skin disease” — these are regulated claims in most markets and trigger AI engine downweighting even when legally fine. Stick to fit and material descriptions.

  7. Pointing schema URLs at non-canonical variant URLs. If the page uses one canonical product URL (e.g. /products/linen-shirt) and variants are query-param selected (?variant=white-m), the hasVariant[].offers.url should still point at the variant-specific URL — that’s where buyers (and AI agents) land.

How to verify

  1. Paste the deployed product URL into

    Google Rich Results Test

    . The Product and BreadcrumbList items should both validate.
  2. Run the validation checklist above against each field.
  3. Open the page in an incognito window and check that the visible product H1, price, availability, and breadcrumb all match what the schema declares.
  4. Re-run the Schema Generator if you change product structure (new color, dropped size) and re-deploy.

The JSON-LD is one signal in the AI-shopping stack. Pair it with the Fashion llms.txt template for the content-map side, and verify that GPTBot can reach the product page using the Robots Analyzer.

Validation checklist

  • Product name in schema matches the visible H1

    The `name` field of ProductGroup matches the H1 on the product page exactly (case + punctuation). AI engines downweight mismatches as a low-trust signal.

  • Each variant has a unique SKU

    No two `hasVariant[].sku` values collide. Shopify auto-generates variant SKUs but merchants often override them — duplicate SKUs in schema cause AI engines to merge variants incorrectly.

  • Variant names identify size / color / material

    Each `hasVariant[].name` reads like a real product label (e.g. 'Linen Relaxed Shirt - White / M'), not a placeholder. AI shopping answers quote this field directly.

  • productGroupID is stable across deploys

    The `productGroupID` should be a stable internal identifier (Shopify product GID or a custom slug), not a random build-time UUID. AI engines use this to deduplicate the same product across page reloads.

  • Every Offer has currency + price + availability + URL

    Each variant's `offers` block has `priceCurrency`, `price`, `availability`, and `url`. Missing currency is the #1 reason Google Rich Results Test downgrades a Product result.

  • AggregateRating only present if real reviews are visible

    If you emit `aggregateRating`, the same rating + review count must be visible on the product page. AI engines compare schema to visible page content; fabricated ratings trigger downweighting.

  • BreadcrumbList matches the visible breadcrumb

    The schema's `itemListElement` URL chain matches the visible breadcrumb on the page exactly (same path, same labels). Mismatches confuse AI engines that use breadcrumb context for category understanding.

  • Rich Results Test returns no critical errors

    After deploying, paste the product URL into https://search.google.com/test/rich-results — the Product and BreadcrumbList items should both validate with no critical errors.

Open in Schema Generator

Prefilled with apparel-store placeholders for product name, description, brand, price, availability, and ratings. Replace placeholders with your real product data and download a Shopify-ready JSON-LD block.

Frequently asked questions

Should a Shopify apparel product use Product or ProductGroup?

Use `Product` for a simple item with no variants. Use `ProductGroup` whenever size, color, or material variants exist — it lets AI engines cite the right variant for a specific query ('linen shirt in size M') instead of conflating them. Shopify's default Liquid templates emit `Product` only; the upgrade to `ProductGroup` is manual but worth the work for any apparel store with multiple sizes.

Can I emit AggregateRating without reviews visible on the page?

No. Schema must mirror visible content. If you emit `aggregateRating: 4.7 from 32 reviews` but the product page shows no review widget, AI engines and Google both flag it as untrustworthy. Either render the reviews visibly (Shopify Apps like Judge.me or native Shopify Reviews) or drop the schema field entirely.

Should each color variant be a separate `hasVariant` entry?

Yes when the variant has distinct SKU, color, size, image, price, or availability. AI shopping engines answering 'is the linen shirt in black still in stock' need each variant to be a separately-addressable Product node with its own `offers.availability`. Color-only variants get one `hasVariant` per color; size-only get one per size; size+color is the Cartesian product.

Does ProductGroup schema replace Shopify product feeds?

No. JSON-LD on the page complements feeds (Shopify's auto-generated `/products.json`, Google Merchant feed, Meta Catalog). AI shopping engines crawl pages directly *and* read feeds — both signals matter. Don't drop one for the other.

Related resources