The correct BreadcrumbList JSON-LD shape
BreadcrumbList is a list of ListItems with position, name, and item (URL). The rules are strict — validators reject small deviations that retrievers also penalise.
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "https://example.com/"
},
{
"@type": "ListItem",
"position": 2,
"name": "Mens Boots",
"item": "https://example.com/collections/mens-boots"
},
{
"@type": "ListItem",
"position": 3,
"name": "Carter Chelsea Boot"
}
]
}The rules that catch stores out
- position is 1-indexed and sequential — 1, 2, 3 with no gaps. Starting at 0 or skipping 2 invalidates the markup.
- item (the URL) is required on every step except the final one (which represents the current page). Many stores include it on the last step too; that is tolerated but not required.
- item URLs must be absolute — https://example.com/collections/x, not /collections/x.
- name must match the visible breadcrumb text — retrievers and validators compare and flag mismatches. No H1 vs breadcrumb name mismatch.
- The entire hierarchy lives in one list — not multiple BreadcrumbList blocks for the same page. If a product belongs to multiple collections, pick one canonical path.
The Shopify Liquid implementation that works on every theme
Drop-in snippet for product and collection pages. Handles the multi-collection case by using the collection the user traversed to reach the product.
Shopify's theme context gives you enough information to emit a correct BreadcrumbList on both product and collection pages. The key variables are collection (populated when the user reaches a product via a collection path), product.collections (the full set of collections a product belongs to), and routes (for building the home URL). Here is the snippet we ship on every Shopify audit.
Product page snippet
{%- liquid
assign breadcrumb_collection = collection
if breadcrumb_collection == blank
assign breadcrumb_collection = product.collections.first
endif
-%}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "{{ shop.url }}{{ routes.root_url }}"
}
{%- if breadcrumb_collection -%}
,
{
"@type": "ListItem",
"position": 2,
"name": {{ breadcrumb_collection.title | json }},
"item": "{{ shop.url }}{{ breadcrumb_collection.url }}"
},
{
"@type": "ListItem",
"position": 3,
"name": {{ product.title | json }}
}
{%- else -%}
,
{
"@type": "ListItem",
"position": 2,
"name": {{ product.title | json }}
}
{%- endif -%}
]
}
</script>Collection page snippet
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "{{ shop.url }}{{ routes.root_url }}"
},
{
"@type": "ListItem",
"position": 2,
"name": {{ collection.title | json }}
}
]
}
</script>Handling products that live in multiple collections
A product in five collections cannot emit five BreadcrumbLists — pick one. Priority: traversal context first, then primary category, then most-specific.
The messiest part of BreadcrumbList on Shopify is choosing which collection to emit when a product belongs to many. A 'Carter Chelsea Boot' might live in Men, Boots, Leather, New Arrivals, Winter 2026, and Gifts Under $200 simultaneously. Emitting all six as breadcrumbs is wrong — BreadcrumbList represents a single path, not a set of paths. The decision rule that works for most stores:
- 1If the user reached the product via a collection URL (collection variable is populated), use that collection. It reflects the actual traversal path.
- 2Otherwise, use the product's primary category. Shopify does not have a built-in 'primary collection' field; the common convention is the first collection in product.collections or a metafield pointing to the canonical category.
- 3If no primary category is set, fall back to the most-specific collection (the one with the deepest sub-category position in your nav).
- 4If the product genuinely has no meaningful category hierarchy, omit the middle breadcrumb and emit just Home > Product. Better than a misleading hierarchy.
Setting a primary collection via metafield
{%- liquid
assign primary = product.metafields.custom.primary_collection.value
if primary
assign breadcrumb_collection = primary
elsif collection != blank
assign breadcrumb_collection = collection
else
assign breadcrumb_collection = product.collections.first
endif
-%}Validating BreadcrumbList schema and catching errors
Google's Rich Results Test is the fastest check. Schema.org validator is stricter. A custom regression test catches silent theme changes.
Once you ship the BreadcrumbList markup, validate it on representative pages before you consider the work done. Three tools matter and they catch different classes of error.
- Google Rich Results Test
- The fastest end-to-end check. Paste your product URL, view the parsed BreadcrumbList, and confirm position, name, and item for each step. Google is permissive — missing fields warn rather than fail.
- Schema.org validator (validator.schema.org)
- Stricter — catches malformed types, missing required fields, and @context issues. Treat as the 'real' validator for correctness.
- Bing Webmaster Tools markup validator
- Useful for catching Bing-specific issues, which matter for Copilot and DuckDuckGo retrieval. Less frequently needed but worth running on major theme changes.
- In-repo regression test
- A Playwright or Cypress test that fetches a product page, parses the JSON-LD, and asserts the BreadcrumbList shape. Catches silent breakage from theme deploys.
Six common BreadcrumbList mistakes on Shopify
Trailing self-URL, wrong position numbering, HTML-mismatched names, and more. Each has a recognisable fingerprint and a quick fix.
- 1Self-referential final item URL — the last breadcrumb's item points to the current page. Tolerated but not clean. Remove the item on the final step.
- 2Position numbering wrong — 0, 1, 2 instead of 1, 2, 3; or gaps like 1, 3, 4. Always sequential starting from 1.
- 3name mismatches visible text — the visible breadcrumb says 'Men' but schema says 'Mens Collection'. Retrievers compare and flag. Make them identical.
- 4Relative URLs in item — /collections/mens instead of https://example.com/collections/mens. Absolute only.
- 5Multiple BreadcrumbList blocks on one page — theme ships default, merchant ships custom, both end up in head. Only one BreadcrumbList per page.
- 6BreadcrumbList emitted on unrelated page types — homepage, cart, checkout. BreadcrumbList belongs on product, collection, article, and search result pages only.
“BreadcrumbList is the schema type that merchants most often ship incorrectly — not because the spec is hard, but because Shopify's multi-collection model doesn't map cleanly to a single-path hierarchy. Getting it right is a 30-minute exercise that pays back for years.”