How to Add Dynamic Breadcrumb Schema in Shopify (JSON-LD)
How to add dynamic breadcrumb schema in Shopify requires inserting JSON-LD structured data that pulls navigation paths automatically from Shopify’s template variables. This guide from HiAgency covers implementation for product pages, collection pages, and blog posts using Liquid code that adapts to your site structure without manual updates.

Breadcrumb schema displays your site hierarchy in search results as clickable navigation paths. Dynamic implementation ensures the markup updates automatically when you reorganize collections, add new categories, or restructure your store navigation.
What Makes Breadcrumb Schema Dynamic in Shopify?
Dynamic breadcrumb schema generates navigation paths automatically from Shopify’s database using Liquid template variables. Static breadcrumbs require hardcoding each URL and title, breaking when you rename collections or move products between categories.
Dynamic implementation benefits:
- Automatic updates – Schema reflects current collection names and product locations without code changes
- Multi-level support – Handles nested collections (Department > Category > Subcategory) automatically
- Template flexibility – Works across different page types (products, collections, pages, blogs) with single implementation
- Maintenance-free – No manual edits needed when restructuring site navigation
Shopify provides template objects (product.collections, collection.url, page.title) that contain current page information. Breadcrumb schema pulls from these objects to generate accurate navigation paths.
How to Add Dynamic Breadcrumb Schema for Product Pages
Product page breadcrumbs show the path from homepage through collection to the specific product. Implementation uses the product’s collection assignment to build the hierarchy.
Step 1: Access Product Template File
Navigate to theme code editor:
- Go to Online Store > Themes
- Click Actions > Edit code
- Open Templates/product.liquid (or sections/main-product.liquid for OS 2.0 themes)
Step 2: Insert Dynamic Product Breadcrumb Schema
Add this code near the closing </body> tag or in the template head:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "{{ shop.url }}"
},
{% if product.collections.size > 0 %}
{
"@type": "ListItem",
"position": 2,
"name": "{{ product.collections.first.title }}",
"item": "{{ shop.url }}{{ product.collections.first.url }}"
},
{% endif %}
{
"@type": "ListItem",
"position": {% if product.collections.size > 0 %}3{% else %}2{% endif %},
"name": "{{ product.title }}",
"item": "{{ shop.url }}{{ product.url }}"
}
]
}
</script>
This code creates a three-level breadcrumb: Home > Collection > Product. The {% if %} conditional handles products not assigned to collections.
Step 3: Handle Multiple Collection Assignments
Products assigned to multiple collections need logic to select the most relevant collection for breadcrumbs:
{% assign current_collection = collection %}
{% if current_collection == blank and product.collections.size > 0 %}
{% assign current_collection = product.collections.first %}
{% endif %}
Add this before the schema block. It prioritizes the collection from which the customer navigated to the product, falling back to the first assigned collection. Updated schema using current_collection:
{
"@type": "ListItem",
"position": 2,
"name": "{{ current_collection.title }}",
"item": "{{ shop.url }}{{ current_collection.url }}"
}
Step 4: Add Nested Collection Support
For stores with parent/child collection hierarchies (using collection metafields or tags), extend the breadcrumb logic:
{% if current_collection.metafields.custom.parent_collection %}
{% assign parent_collection_handle = current_collection.metafields.custom.parent_collection %}
{% assign parent_collection = collections[parent_collection_handle] %}
{
"@type": "ListItem",
"position": 2,
"name": "{{ parent_collection.title }}",
"item": "{{ shop.url }}{{ parent_collection.url }}"
},
{
"@type": "ListItem",
"position": 3,
"name": "{{ current_collection.title }}",
"item": "{{ shop.url }}{{ current_collection.url }}"
},
{% endif %}
This assumes you store parent collection handles in a custom metafield. Adjust the metafield namespace and key to match your setup.
How to Add Dynamic Breadcrumb Schema for Collection Pages
Collection page breadcrumbs show Home > Collection (or Home > Parent Collection > Collection for nested structures).
Step 1: Open Collection Template
Access collection template file:
- In theme code editor, open Templates/collection.liquid
- For OS 2.0 themes, edit sections/main-collection.liquid
Step 2: Insert Collection Breadcrumb Schema
Add this code to the collection template:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "{{ shop.url }}"
},
{
"@type": "ListItem",
"position": 2,
"name": "{{ collection.title }}",
"item": "{{ shop.url }}{{ collection.url }}"
}
]
}
</script>
The collection object provides title and URL automatically. This creates a two-level breadcrumb for standard collections.
Step 3: Implement Parent Collection Logic
For nested collections, add parent collection detection:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "{{ shop.url }}"
},
{% if collection.metafields.custom.parent_collection %}
{% assign parent_handle = collection.metafields.custom.parent_collection %}
{% assign parent = collections[parent_handle] %}
{
"@type": "ListItem",
"position": 2,
"name": "{{ parent.title }}",
"item": "{{ shop.url }}{{ parent.url }}"
},
{
"@type": "ListItem",
"position": 3,
"name": "{{ collection.title }}",
"item": "{{ shop.url }}{{ collection.url }}"
}
{% else %}
{
"@type": "ListItem",
"position": 2,
"name": "{{ collection.title }}",
"item": "{{ shop.url }}{{ collection.url }}"
}
{% endif %}
]
}
</script>
This checks for a parent_collection metafield and builds three-level breadcrumbs when present, falling back to two levels for top-level collections.
How to Add Dynamic Breadcrumb Schema for Blog Posts
Blog post breadcrumbs follow the pattern: Home > Blog > Article Title.
Step 1: Access Article Template
Open the article template file:
- Navigate to Templates/article.liquid
- For OS 2.0 themes, edit sections/main-article.liquid
Step 2: Insert Article Breadcrumb Schema
Add this code to the article template:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "{{ shop.url }}"
},
{
"@type": "ListItem",
"position": 2,
"name": "{{ blog.title }}",
"item": "{{ shop.url }}{{ blog.url }}"
},
{
"@type": "ListItem",
"position": 3,
"name": "{{ article.title }}",
"item": "{{ shop.url }}{{ article.url }}"
}
]
}
</script>
The blog and article objects provide titles and URLs automatically. This creates a three-level breadcrumb for all blog posts.
Step 3: Handle Multiple Blogs
If your store uses multiple blogs (e.g., News, Guides, Updates), the schema adapts automatically because {{ blog.title }} pulls the current blog name:
- Post in “News” blog: Home > News > Article Title
- Post in “Guides” blog: Home > Guides > Article Title
No additional code needed for multi-blog support.
How to Add Dynamic Breadcrumb Schema for Standard Pages
Standard pages (About Us, Contact, FAQ) use simple two-level breadcrumbs: Home > Page Title.
Step 1: Open Page Template
Access the page template:
- Open Templates/page.liquid
- For OS 2.0 themes, edit sections/main-page.liquid
Step 2: Insert Page Breadcrumb Schema
Add this code to the page template:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "{{ shop.url }}"
},
{
"@type": "ListItem",
"position": 2,
"name": "{{ page.title }}",
"item": "{{ shop.url }}{{ page.url }}"
}
]
}
</script>
The page object provides title and URL. This works for all standard pages automatically.
How to Create Universal Dynamic Breadcrumb Schema
Universal implementation adds breadcrumb schema to all page types from a single code block in theme.liquid.
Step 1: Open Theme Layout File
Access the main theme file:
- In theme code editor, open Layout/theme.liquid
- Locate the closing </head> or </body> tag
Step 2: Insert Universal Breadcrumb Schema
Add this comprehensive code block:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "{{ shop.url }}"
}
{% if template contains 'product' %}
{% if product.collections.size > 0 %}
,{
"@type": "ListItem",
"position": 2,
"name": "{{ product.collections.first.title }}",
"item": "{{ shop.url }}{{ product.collections.first.url }}"
}
{% endif %}
,{
"@type": "ListItem",
"position": {% if product.collections.size > 0 %}3{% else %}2{% endif %},
"name": "{{ product.title }}",
"item": "{{ shop.url }}{{ product.url }}"
}
{% elsif template contains 'collection' %}
,{
"@type": "ListItem",
"position": 2,
"name": "{{ collection.title }}",
"item": "{{ shop.url }}{{ collection.url }}"
}
{% elsif template contains 'article' %}
,{
"@type": "ListItem",
"position": 2,
"name": "{{ blog.title }}",
"item": "{{ shop.url }}{{ blog.url }}"
}
,{
"@type": "ListItem",
"position": 3,
"name": "{{ article.title }}",
"item": "{{ shop.url }}{{ article.url }}"
}
{% elsif template contains 'page' %}
,{
"@type": "ListItem",
"position": 2,
"name": "{{ page.title }}",
"item": "{{ shop.url }}{{ page.url }}"
}
{% endif %}
]
}
</script>
This single block handles products, collections, blog posts, and pages. The template conditional determines which breadcrumb structure to generate.
Step 3: Exclude Homepage from Schema
Wrap the entire schema block in a conditional to prevent breadcrumbs on the homepage:
{% unless template == 'index' %}
<script type="application/ld+json">
...breadcrumb schema code...
</script>
{% endunless %}
Homepages don’t need breadcrumbs since they’re the starting point of navigation.
How to Handle Search Result Pages in Breadcrumb Schema
Search results pages require special handling because they don’t fit standard navigation hierarchies.
Step 1: Detect Search Template
Add search detection to universal schema:
{% elsif template contains 'search' %}
,{
"@type": "ListItem",
"position": 2,
"name": "Search Results",
"item": "{{ shop.url }}{{ request.path }}"
}
{% endif %}
Step 2: Include Search Query in Breadcrumb
Display the actual search term in breadcrumbs:
{% elsif template contains 'search' %}
,{
"@type": "ListItem",
"position": 2,
"name": "Search Results{% if search.terms %}: {{ search.terms }}{% endif %}",
"item": "{{ shop.url }}{{ request.path }}"
}
{% endif %}
This creates breadcrumbs like “Home > Search Results: running shoes” for search pages.
How to Validate Dynamic Breadcrumb Schema
Testing ensures dynamic breadcrumbs generate correctly across different page types.
Step 1: Test Multiple Page Types
Visit and validate these pages:
- Product page – Verify collection appears in breadcrumb
- Collection page – Check collection title displays
- Blog post – Confirm blog name and article title show
- Standard page – Test page title appears
For each page, view source and search for “BreadcrumbList” to inspect the generated schema.
Step 2: Use Google Rich Results Test
Validate each page type:
- Go to search.google.com/test/rich-results
- Enter page URL
- Click Test URL
- Verify “Breadcrumb” detected in results
Check that position numbers increment correctly and item URLs are complete (include https:// and domain).
Step 3: Verify Liquid Variable Output
Common issues with dynamic schema: Empty collection titles – Products not assigned to collections produce blank breadcrumb items. Use {% if product.collections.size > 0 %} conditionals to prevent this. Missing shop.url – URLs show as /collections/shoes instead of https://store.com/collections/shoes. Always prefix with {{ shop.url }}. Special characters in titles – Product or collection names with quotes break JSON syntax. Use | escape filter: “name”: “{{ product.title | escape }}”.
How to Add Breadcrumb Schema Using Shopify Apps
Schema apps provide dynamic breadcrumb implementation without code editing.
Step 1: Install a Schema App
Three apps support dynamic breadcrumbs: JSON-LD for SEO:
- Free plan available
- Automatic breadcrumb generation for all page types
- Handles nested collections via metafields
Schema Plus for SEO:
- $5.99/month
- Visual breadcrumb configurator
- Custom breadcrumb rules per template
Smart SEO:
- $4.99/month
- Breadcrumb schema included with full SEO suite
- Multi-language breadcrumb support
Step 2: Configure Breadcrumb Settings
In app settings:
- Enable Breadcrumb Schema toggle
- Select page types to include (products, collections, blogs, pages)
- Configure collection priority for products in multiple collections
- Set homepage label (Home, Homepage, Main Page)
Step 3: Test App-Generated Breadcrumbs
Verify app output:
- Visit different page types
- View page source
- Locate BreadcrumbList schema
- Check accuracy of names and URLs
Most apps generate schema dynamically using the same Liquid variables as manual implementation.
How to Fix Common Dynamic Breadcrumb Schema Errors
Position Numbers Skip or Duplicate
Error: Position goes 1, 2, 4 or shows 2, 2, 3 Cause: Conditional logic breaks position numbering Fix: Use dynamic position calculation:
{% assign position = 1 %}
{
"@type": "ListItem",
"position": {{ position }},
"name": "Home",
"item": "{{ shop.url }}"
}
{% assign position = position | plus: 1 %}
{% if product.collections.size > 0 %}
,{
"@type": "ListItem",
"position": {{ position }},
"name": "{{ product.collections.first.title }}",
"item": "{{ shop.url }}{{ product.collections.first.url }}"
}
{% assign position = position | plus: 1 %}
{% endif %}
Increment position variable only when adding breadcrumb items.
Trailing Commas in JSON
Error: “Unexpected token” or JSON parse error Cause: Extra comma after last breadcrumb item Fix: Remove trailing commas from conditional blocks:
{% if product.collections.size > 0 %}
,{
"@type": "ListItem",
"position": 2,
"name": "{{ product.collections.first.title }}",
"item": "{{ shop.url }}{{ product.collections.first.url }}"
}
{% endif %}
Notice the comma before the opening brace, not after the closing brace.
Missing URLs in Breadcrumb Items
Error: “Missing field ‘item’ (required)” Cause: Collection or product URL is blank Fix: Always include {{ shop.url }} prefix:
"item": "{{ shop.url }}{{ collection.url }}"
Never use collection.url alone, as it returns a relative path (/collections/shoes) not a full URL.
Common Questions About Dynamic Breadcrumb Schema in Shopify
How Do Dynamic Breadcrumbs Handle Products in Multiple Collections?
Products assigned to multiple collections need logic to select which collection appears in breadcrumbs. Shopify doesn’t automatically determine the “primary” collection, so you must implement priority rules.
The most effective approach uses the collection context. When customers navigate from a collection page to a product, that collection should appear in breadcrumbs:
{% assign breadcrumb_collection = collection %}
{% if breadcrumb_collection == blank %}
{% assign breadcrumb_collection = product.collections.first %}
{% endif %}
This checks if a collection context exists (customer came from a collection page). If blank, it falls back to the first assigned collection. For more control, prioritize featured collections using product metafields:
{% if product.metafields.custom.primary_collection %}
{% assign primary_handle = product.metafields.custom.primary_collection %}
{% assign breadcrumb_collection = collections[primary_handle] %}
{% elsif collection %}
{% assign breadcrumb_collection = collection %}
{% else %}
{% assign breadcrumb_collection = product.collections.first %}
{% endif %}
This hierarchy prioritizes: 1) manually assigned primary collection from metafield, 2) collection from navigation context, 3) first alphabetically assigned collection. For products with parent/child collection relationships, add logic to detect collection hierarchy and build multi-level breadcrumbs accordingly.
Can Breadcrumb Schema Display Collection Filters or Sort Parameters?
Breadcrumb schema should not include filter or sort parameters in URLs. Google expects breadcrumbs to represent the site’s content hierarchy, not user session state or URL parameters. Incorrect implementation including filters:
"item": "{{ shop.url }}{{ collection.url }}?sort_by=price-ascending"
This creates different breadcrumb URLs for the same collection depending on how users sorted products, which confuses search engines about your site structure. Correct implementation uses canonical collection URLs:
"item": "{{ shop.url }}{{ collection.url }}"
Even if customers arrived at a product through filtered collection views (e.g., /collections/shoes?color=blue), breadcrumbs should reference the base collection URL (/collections/shoes). For breadcrumb visual display (the actual HTML breadcrumbs shown to users), you can include filter context. But the structured data schema should always point to canonical URLs without parameters.
Where Should Dynamic Breadcrumb Schema Code Go – Theme.liquid or Individual Templates?
Theme.liquid placement centralizes breadcrumb schema in one location, making maintenance easier but requiring more complex conditionals to handle different page types. Individual template placement separates schema by page type, simplifying logic but requiring updates across multiple files. Choose theme.liquid when:
- You want single-point maintenance for all breadcrumbs
- Your store uses consistent navigation patterns across page types
- You’re comfortable with Liquid conditionals ({% if template contains ‘product’ %})
Choose individual templates when:
- Different page types need unique breadcrumb logic
- You prefer simpler, template-specific code
- You’re implementing breadcrumbs gradually across page types
Best practice: Start with individual templates to perfect breadcrumb logic for each page type, then consolidate into theme.liquid once patterns are established. This approach minimizes debugging complexity. For OS 2.0 themes, template files often just reference sections, so breadcrumb schema must go in section files (sections/main-product.liquid) rather than template files. Check your theme architecture before deciding placement.
Do Dynamic Breadcrumbs Work with Shopify Markets and Multiple Languages?
Dynamic breadcrumb schema works with Shopify Markets but requires language-aware implementation to display translated collection and product names in breadcrumbs. Shopify’s built-in translation system provides localized values for product.title and collection.title automatically when using Markets. Your existing dynamic breadcrumb code adapts to the current language without modifications:
{
"@type": "ListItem",
"position": 2,
"name": "{{ collection.title }}",
"item": "{{ shop.url }}{{ collection.url }}"
}
This outputs “Zapatos” for Spanish customers viewing a shoes collection, and “Shoes” for English customers, because {{ collection.title }} returns the translated value based on the customer’s language context. URLs must include the language prefix for non-default languages. Shopify’s {{ collection.url }} handles this automatically:
- English (default): /collections/shoes
- Spanish: /es/collections/zapatos
- French: /fr/collections/chaussures
For stores using third-party translation apps (Langify, Weglot), verify that the app’s Liquid filters apply to breadcrumb schema. Some apps require explicit translation filters:
"name": "{{ collection.title | t }}"
Check your translation app’s documentation for schema compatibility. Most modern apps handle JSON-LD structured data automatically.
How Often Should You Update Dynamic Breadcrumb Schema Code?
Dynamic breadcrumb schema requires updates only when Shopify changes Liquid template variables or you restructure your site navigation hierarchy. The code itself updates breadcrumb content automatically as you add products and collections.
Review breadcrumb implementation quarterly to ensure: Liquid variables remain valid – Shopify occasionally deprecates template objects or changes syntax (like the OS 2.0 transition). Check Shopify’s developer changelog for breaking changes. Navigation structure matches breadcrumb logic – If you add collection hierarchies or new page types (lookbook pages, landing pages), update breadcrumb conditionals to include these templates. Schema.org specifications stay current – BreadcrumbList schema is stable, but Google occasionally updates required or recommended properties. Monitor Search Console for warnings. Immediate updates needed when:
- Migrating to OS 2.0 theme structure – Template and section architecture changes require relocating breadcrumb code
- Implementing nested collections – Add parent collection logic to support multi-level breadcrumbs
- Adding new template types – Create breadcrumb patterns for custom page templates
Unlike static breadcrumbs that break when you rename collections, dynamic breadcrumbs maintain accuracy without manual edits. The “dynamic” aspect means minimal ongoing maintenance.Need expert help with Shopify SEO? HiAgency’s technical SEO specialists implement schema markup, optimize site architecture, and deliver data-driven strategies that improve organic visibility. Get in touch to discuss your Shopify store’s SEO performance.
Comments
Post a Comment