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.

Developer implementing dynamic breadcrumb schema in Shopify store navigation structure

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. Website hierarchy diagram showing dynamic breadcrumb navigation levels 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. Ecommerce product page displaying breadcrumb navigation above product titleStep 1: Access Product Template File

Navigate to theme code editor:

  1. Go to Online Store > Themes
  2. Click Actions > Edit code
  3. 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:

  1. In theme code editor, open Templates/collection.liquid
  2. 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:

  1. Navigate to Templates/article.liquid
  2. 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:

  1. Open Templates/page.liquid
  2. 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. Flow chart illustrating universal dynamic breadcrumb schema logic across templatesStep 1: Open Theme Layout File

Access the main theme file:

  1. In theme code editor, open Layout/theme.liquid
  2. 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. Google Rich Results Test interface validating breadcrumb structured dataStep 1: Test Multiple Page Types

Visit and validate these pages:

  1. Product page – Verify collection appears in breadcrumb
  2. Collection page – Check collection title displays
  3. Blog post – Confirm blog name and article title show
  4. 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:

  1. Go to search.google.com/test/rich-results
  2. Enter page URL
  3. Click Test URL
  4. 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:

  1. Enable Breadcrumb Schema toggle
  2. Select page types to include (products, collections, blogs, pages)
  3. Configure collection priority for products in multiple collections
  4. Set homepage label (Home, Homepage, Main Page)

Step 3: Test App-Generated Breadcrumbs

Verify app output:

  1. Visit different page types
  2. View page source
  3. Locate BreadcrumbList schema
  4. 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

Popular posts from this blog

SEO for Beauty Ecommerce: How to Rank Your Beauty Store in 2025

Ecommerce Voice Search: 7 Optimization Strategies for Smart Speakers

Local SEO for Roofers: Get More Roofing Jobs in Australia