Shopify Breadcrumbs Based on Menu Structure (Improved Version)

Please note: This code is now quite old in Shopify years, which are even shorter than dog years. There have been updates and expansions to Liquid and Shopify and the entire Online Store 2.0 platform has launched. I now recommend that people upgrade to a modern 2.0 theme with breadcrumb support and ignore this entire post. I have no interest in and no plan to improve or update this code. It is what it is. If it works, wonderful. If not, you’re on your own. If you still want to proceed, read on MacDuff….

A couple of years ago, I wrote about how to create breadcrumbs for products that would mirror the main menu of a Shopify site. In reality, that was a quick and dirty method that I had to quickly update a month later, but never got around to writing up the better version.

As I noted there, the design goals were

  • Easy maintenance for shopkeepers. Shopkeepers just have to edit their navigation as normal and breadcrumbs happen automatically.
  • Intuitive for customers. Breadcrumbs match the site nav. Simple
  • Decent abstraction and minimal coupling. We don’t achieve this by asking shopkeepers to mix product types and collections and tags in some simulated hierarchy prone to breaking.

The original version was the quick route, but it had some unnecessary deficits when it came to the last point. The main problem was that an item had to belong to a top-level navigation item (i.e. collection) in order to get a proper breadcrumb. That violates the basic principle of being flexible in the data you accept and strict in the data you output.

The reason for this shortcut was because data structures that one would take for granted in any proper programming language, are not available in a simple templating language like Liquid. This makes the whole thing super cumbersome.

What’s New in Better Breadcrumbs?

In the original version, I created an array of all collections the item belonged to, then compared that with all the top-level navigation links until I found a match.

It quickly became obvious that a real shop commonly does not meet that requirement. Items may only appear in, say, the third level of nav and not belong to any collection.

So the improved method does the following:

  • Scans the entire menu hierarchy and identifies all child menus and all links in all child menus.
  • For any given URL (product, collection, page), we do a lookup to see which menu it appears in. If the product does not appear directly, we look to see if a collection that contains the product is linked in a menu.
  • Generate the breadcrumb based on what we found.

Limited to Three Levels.

We are limited to three levels. This is a basic Shopify limitation, so we couldn’t go deeper if we wanted to.

However, given how cumbersome this is, even if Shopify changes, we wouldn’t want to go deeper. We lack basic data structures and have no ability to do recursion in Liquid, so we need to hardcode the number of levels.

Frankly, navigation that goes deeper than three levels probably needs rethinking anyway (search, filters, faceted search, etc would all be better options).

Note also that Shopify has since added some enhancements to Liquid, in particular the linklist.levels attribute. That would have been a huge help if it had existed when I wrote this code originally, but I don’t think it’s worth rewriting it. But it could probably be simpler if it took advantage of these.

A few resources

A Newer Better Version

I mentioned in above that Shopify had added the linklist.levels attribute, which would have dramatically simplified this code had it been available when I wrote the original. BlameItOnJoe took that tip and cranked out a new, simplified version. The problem is that people report that it does not work for products, only on collection pages.

Meanwhile, some people have had troubles with the original code working in collections that are a couple levels deep.

Patrick took both methods and added a simple conditional statement to use my code on product pages and BlameItOnJoe’s code on collection pages. You can see his explanation in the comments below. Patrick’s combined code should be plug and play for the vast, vast majority of sites.

The Original Code

I’m just going to dump this all in one big field so it’s easier to copy and paste. I have pretty extensive comments within the code. If that’s not enough, write to me and I’ll add more comments.

In addition, I’ve added it as a github repo, so you can download it that way.

<!-- /snippets/breadcrumb.liquid -->
{%- comment -%}
  Documentation - http://docs.shopify.com/support/your-website/navigation/creating-a-breadcrumb-navigation
{%- endcomment -%}
{%- unless template == 'index' or template == 'cart' -%}
<nav class="breadcrumb" role="navigation" aria-label="breadcrumbs">
  <a href="/" title="{{ 'general.breadcrumbs.home_link_title' | t }}">{{ 'general.breadcrumbs.home' | t }}</a>

      {%comment%} DETERMINE OBJECT TYPE - product, collection or page   {%endcomment%}
  
{%- if template contains 'product' -%}
  {%- capture product_url_string -%}{%- for collection in product.collections -%}{{collection.url }}|{%- endfor -%}{%- endcapture -%}
  {%- assign object_url_string = product_url_string | append: product.url -%}
{%- elsif template contains 'collection' and collection.handle -%}
  {%- capture object_url_string -%}/collections/{{ collection.handle }}{%- endcapture -%}
{%- elsif template contains 'page' -%}
  {%- capture object_url_string -%}/pages/{{ page.url }}{%- endcapture -%}
{% endif %}

{%- assign object_urls = object_url_string | split: '|' -%}

  {%comment%} 
  GET TITLES OF ALL LINKLISTS
  Linklist is the Shopify term for a menu or submenu. We grab all the titles so we can match against them and
  find out if they are linked from a parent menu.
  {%endcomment%}    
{%- capture linklist_titles_str -%}{%- for linklist in linklists -%}{{ linklist.title | handleize }}|{%- endfor -%}{%- endcapture -%}
{%- assign str_size = linklist_titles_str | size | minus: 1 -%}
{%- assign linklist_titles_str = linklist_titles_str | slice: 0, str_size -%}
{%- assign linklist_titles = linklist_titles_str | split: '|' -%}

  {%comment%} 
  GET TOP LEVEL MENU THEN DRILL DOWN
  First we grab all the top-level menu items and we create a set of strings for each property, which 
  we will eventually split into arrays. We cannot create multi-dimensional arrays in Liquid, but we 
  can create parallel arrays so that if we know the index in the handle array, we can find it's title
  by iterating through the title array until we hit the same index.
  
  If the link handle matches a string in the linklist_titles, the list with that title is the child.
  You might think we would match handles to handles, but we don't. Shopify creates nested menus based
  on the parent item having the exact same label as the child item and converts that to a handle.
  {%endcomment%}

{%- assign level = 1 -%}
{%- for link in linklists.main-menu.links -%}
  {%- assign link_handle = link.title | handle -%}
  {%- assign link_titles = link_titles | append: link.title | append: '|' -%}
  {%- assign link_urls = link_urls | append: link.url | append: '|' -%}
  {%- assign link_levels = link_levels | append: level | append: '|'  -%}
  {%- assign link_parents = link_parents | append: 'main-menu' | append: '|'  -%}
  {%- assign link_handles = link_handles | append: link_handle | append: '|' -%}

  {%- if linklist_titles contains link_handle -%}

      {%comment%} GET CHILDREN    {%endcomment%}

    {%- for clink in linklists[link_handle].links -%}
      {%- if forloop.first == true -%}
        {%- assign level = level | plus: 1 -%}
      {%- endif -%}
      {% assign clink_handle = clink.title | handle %}
      {%- assign link_titles = link_titles | append: clink.title | append: '|' -%}
      {%- assign link_urls = link_urls | append: clink.url | append: '|' -%}
      {%- assign link_levels = link_levels | append: level | append: '|'  -%}
      {%- assign link_parents = link_parents | append: link_handle | append: '|'  -%}
      {%- assign handle = link.title | handleize -%} 
      {%- assign link_handles = link_handles | append: clink_handle | append: '|' -%}

      {%- if linklist_titles contains clink_handle -%}

              {%comment%} GET GRANDCHILDREN  {%endcomment%}

            {%- for gclink in linklists[clink_handle].links -%}
              {%- if forloop.first == true -%}
                {%- assign level = level | plus: 1 -%}
              {%- endif -%}
              {% assign gclink_handle = gclink.title | handle %}
              {%- assign link_titles = link_titles | append: gclink.title | append: '|' -%}
              {%- assign link_urls = link_urls | append: gclink.url | append: '|' -%}
              {%- assign link_levels = link_levels | append: level | append: '|'  -%}
              {%- assign link_parents = link_parents | append: clink_handle | append: '|'  -%}
              {%- assign link_handles = link_handles | append: gclink_handle | append: '|' -%}

              {%- if linklist_titles contains gclink_handle -%}
                  {% comment %} ************GET ME THE GGC {% endcomment %}
              {%- endif -%}
              {%- if forloop.last == true -%}
                {%- assign level = level | minus: 1 -%}
              {%- endif -%}
            {%- endfor -%}
                {%comment%} END GRANDCHILDREN  {%endcomment%}

      {%- endif -%}
      {%- if forloop.last == true -%}
        {%- assign level = level | minus: 1 -%}
      {%- endif -%}
    {%- endfor -%}
              {%comment%} END CHILDREN  {%endcomment%}
  {%- endif -%}
{%- endfor -%}

{%- comment -%} CONVERT TO ARRAYS {%- endcomment -%}
{%- assign str_size = link_levels | size | minus: 1 -%}
{%- assign llevels = link_levels | slice: 0, str_size | split: '|' -%}

{%- assign str_size = link_titles | size | minus: 1 -%}
{%- assign ltitles = link_titles | slice: 0, str_size | split: '|' -%}

{%- assign str_size = link_handles | size | minus: 1 -%}
{%- assign lhandles = link_handles | slice: 0, str_size | split: '|' -%}

{%- assign str_size = link_parents | size | minus: 1 -%}
{%- assign lparents = link_parents | slice: 0, str_size | split: '|' -%}

{%- assign str_size = link_urls | size | minus: 1 -%}
{%- assign lurls = link_urls | slice: 0, str_size | split: '|' -%}

{%- comment -%} GET THE DEEPEST LEVEL WE FOUND {%- endcomment -%}
{%- assign depth = '3' -%}
{%- assign bc3_parent_list_handle = '' %}

{%- comment -%} LEVEL 3: THREE DOWN FROM THE TOP {%- endcomment -%}
{%- comment -%} Do we have a link to this product or its collection on the deepest level? {%- endcomment -%}
{%- for url in lurls -%}
  {%- if object_urls contains url and llevels[forloop.index0] == depth -%}
    {%- unless url == product.url or url == collection.url -%}
      {%- capture bc3 -%}{{ ltitles[forloop.index0] | link_to: url, ltitles[forloop.index0] }}{%- endcapture -%}
    {%- endunless -%}
    {%- assign bc3_parent_list_handle = lparents[forloop.index0] -%}
    {%- assign bc3_list_handle = lhandles[forloop.index0] -%}
    {% break %}
  {%- endif -%}
{%- endfor -%}

{%- comment -%} LEVEL 2: TWO DOWN FROM THE TOP
  Next level. If we found something, we can save a lot of processing. Otherwise, 
  we need to scan everything on this level 
{%- endcomment -%}
{%- assign depth = '2' -%}
{%- assign bc2_parent_list_handle = '' %}

{%- comment -%} IF we didn't find anything on the deepest level, we start from scratch one level up {%- endcomment -%}
{%- if bc3_parent_list_handle == '' -%} 
  {%- for url in lurls -%}
    {%- if llevels[forloop.index0] == depth -%}
      {%- if object_urls contains url -%} 
        {%- unless url == product.url or url == collection.url -%}
          {%- capture bc2 -%}{{ ltitles[forloop.index0] | link_to: url, ltitles[forloop.index0] }}{%- endcapture -%}
        {% endunless %}
        {%- assign bc2_parent_list_handle = lparents[forloop.index0] -%}
        {%- break -%}
      {%- endif -%}
    {%- endif -%}
  {%- endfor -%}
  
{%- comment -%} ELSE we have a parent handle. We need its array index to find its other data {%- endcomment -%}  
{%- else -%}
  {%- for list_handle in lhandles -%}
    {%- if list_handle == bc3_parent_list_handle -%}
      {% assign bc2_list_handle = list_handle %}
      {%- assign bc2_parent_list_handle = lparents[forloop.index0] -%}
      {%- assign bc2_list_title = ltitles[forloop.index0] -%}
      {%- comment -%} we have the handle of bc2's parent, so now we get the parent links, find where title matches parent title, then we have the collection URL {%- endcomment -%}
      {%- for bc2_sibling_link in linklists[bc2_parent_list_handle].links -%}
        {%- assign bc2_sibling_title_handleized = bc2_sibling_link.title | handle -%}
        {% if bc2_sibling_title_handleized == bc2_list_handle %}
          {%- capture bc2 -%}{{ bc2_sibling_link.title | link_to: bc2_sibling_link.url, bc2_sibling_link.title }}{%- endcapture -%}
          {% break %}
        {%- endif -%}
      {%- endfor -%}
      {% break %}
    {%- endif -%}
  {%- endfor -%}
{%- endif -%}

{%- comment -%} LEVEL 1: MAIN ITEMS BELOW MAIN MENU (I.E. TOP NAV BAR) {%- endcomment -%}

{%- assign depth = depth | minus: 1 | append: '' -%}
{%- assign bc1_parent_list_handle = '' %}

{%- if bc2_parent_list_handle == '' -%} 
    {%- comment -%} IF found nothing on deepest level or middle level, we start from scratch on top level {%- endcomment -%}
  {% for url in lurls %}
    {%- if object_urls contains url and llevels[forloop.index0] == depth -%}
      {%- unless url == product.url or url == collection.url -%}
        {%- capture bc1 -%}{{ ltitles[forloop.index0] | link_to: url, ltitles[forloop.index0] }}{%- endcapture -%}
      {% endunless %}
      {%- assign bc1_parent_list_handle = lparents[forloop.index0] -%}
      {%- break -%}
    {%- endif -%}
  {%- endfor -%}

    {%- comment -%}
        ELSE we have a parent handle. We need its array index to find its other data 
    {%- endcomment -%}
{%- else -%}
  {%- for list_handle in lhandles -%}
    {%- if bc2_parent_list_handle == list_handle -%}
      {% assign bc1_list_handle = list_handle %}
      {%- assign bc1_parent_list_handle = lparents[forloop.index0] -%}
      {%- assign bc1_title = ltitles[forloop.index0] -%}
            {%- comment -%} 
              We HAVE PARENT, so now we GET THE SIBLING links, find where title 
              matches parent title, then we have the collection URL 
            {%- endcomment -%}
      {%- for bc1_sibling_link in linklists[bc1_parent_list_handle].links -%}
        {%- assign bc1_sibling_title_handleized = bc1_sibling_link.title | handle -%}
        {% if bc1_sibling_title_handleized == bc1_list_handle %}
          {%- capture bc1 -%}{{ bc1_sibling_link.title | link_to: bc1_sibling_link.url, bc1_sibling_link.title }}{%- endcapture -%} 
          {% break %}
        {%- endif -%}
      {%- endfor -%}
    {%- endif -%}
  {%- endfor -%}
{%- endif -%}

  {%- if bc1 -%}
    <span aria-hidden="true">›</span>
    {{ bc1 }}
  {%- endif -%}
  {%- if bc2 -%}
    <span aria-hidden="true">›</span>
    {{ bc2 }}
  {%- endif -%}
  {%- if bc3 -%}
    <span aria-hidden="true">›</span>
    {{ bc3 }}
  {%- endif -%}
{%- if template contains 'product' -%}
  <span aria-hidden="true">›</span>
  <span>{{ product.title }}</span>

{%- elsif template contains 'collection' and collection.handle -%}
    <span aria-hidden="true">›</span>
    <span>{{ collection.title }}</span>

{%- elsif template == 'blog' -%}

  <span aria-hidden="true">›</span>
  {%- if current_tags -%}
    {{ blog.title | link_to: blog.url }}
    <span aria-hidden="true">›</span>
    <span>{{ current_tags | join: " + " }}</span>
  {%- else -%}
  <span>{{ blog.title }}</span>
  {%- endif -%}

{%- elsif template == 'article' -%}

  <span aria-hidden="true">›</span>
  {{ blog.title | link_to: blog.url }}
  <span aria-hidden="true">›</span>
  <span>{{ article.title }}</span>

{%- elsif template contains 'page' -%}

 <span aria-hidden="true">›</span>
 <span>{{ page.title }}</span>

{%- else -%}

 <span aria-hidden="true">›</span>
 <span>{{ page_title }}</span>

{% endif %}
</nav>
{%- endunless -%}

Alternate Method

This is an alternate method offered by BlameItOnJoe in the comments, where WordPress mangled the formatting so I’m adding his solution here. I haven’t tried it myself, so I’d be curious if people have good luck with it. One change: Joe’s original code had rel=”nofollow ugc” attributes, which is, I assume, an attempt at pagerank sculpting, but that doesn’t really make sense. There is no reason to nofollow your own navigation and it most definitely is not ugc (user-generated content). All it does is bleeds pagerank and prevents crawling. So I have removed those. You can get his original code on github.

{%- unless template == 'index' or template == 'cart' -%}
  <a href="/" title="{{ 'general.breadcrumbs.home_link_title' | t }}">{{ 'general.breadcrumbs.home' | t }}</a>
  {% for link in linklists.main-menu.links %}
    {% if link.child_active or link.active %}
    ›
      <a href="{{ link.url }}">
      {{ link.title | escape }}  
      </a>
      {% for clink in link.links %}
        {% if clink.child_active or clink.active %}
        ›
          <a href="{{ clink.url }}">
          {{ clink.title | escape }} 
          </a>
          {% for gclink in clink.links %}
            {% if gclink.child_active or gclink.active %}
              ›
              <a href="{{ gclink.url }}">
              {{ gclink.title | escape }} 
              </a>
            {% endif %}
          {%- endfor -%}
        {% endif %}
      {%- endfor -%}
    {% endif %}
  {%- endfor -%}
{%- endunless -%}

70 thoughts on “Shopify Breadcrumbs Based on Menu Structure (Improved Version)”

  1. Thanks for the code! Only issue is I’m seeing “translation missing: en.general.breadcrumbs.home › Shop by Category › Kitchen + Dining › Kitchen + Dining”. Any idea how to fix it? Also, can you share how to indent the breadcrumbs in from the left edge of the page as well as how to make the font smaller?

    Reply
  2. The translation missing error is because you are missing some translation strings are missing from your theme. So if you did not have a translation for a “Home” breadcrumb in your theme to start with, this code will not add one. You’ll have to edit the theme language files.

    Have a look at the Shopify documentation on translation
    https://help.shopify.com/en/manual/using-themes/translate-theme#search-a-translation-for-missing-or-completed-fields

    As for the styling (fonts, margins), that’s just basic CSS. Honestly, if you don’t know how to do that, you probably shouldn’t be playing with code like this. This tutorial is really meant for developers.

    Reply
    • You can also delete this section:

      <a href="/" title="{{ 'general.breadcrumbs.home_link_title' | t }}" rel="nofollow ugc">{{ 'general.breadcrumbs.home' | t }}</a>

      and add this:

      <a href="/" title="Home" rel="nofollow ugc">Home</a>

      Reply
      • That’s a simple solution. Even better, I would say, would be to let the user set the title dynamically, and just get rid of the translation filter.

        This should work too, just with no internationalization support:

        <a href="/" title="{{ 'general.breadcrumbs.home_link_title' }}">{{ 'general.breadcrumbs.home' | t }}</a>

        You can see more on the translation filter here:
        https://shopify.dev/docs/themes/liquid/reference/filters/additional-filters#t-translation

        Also, I didn’t notice that code from BlameItOnJoe had rel=”nofollow ugc” attributes on all links, which I don’t think makes sense. I just updated that.

        Reply
        • I do have a problem, when my product has tags the breadcrumbs shows perfectly Home > All Collection > Collection > Product. But when the product does not have tag the display of the breadcrumbs is Home > All Collection > Product

          Reply
  3. Thank you for your generosity Tom! I was truly looking for a better breadcrumb solution.
    Basic n00b question (WordPress person dipping her toes in Shopify): My theme already has a “breadcrumb-nav.liquid” snippet. Can I throw your code in there? Or overwrite the current code with yours?

    Reply
  4. Hi Caitlin. That’s the general idea. Everything you need in the navigation should be there already, so my code should go where you want the breadcrumb to appear. You might need to tweak this and that. I’ve only used it on one site – https://www.traildesigns.com – so I don’t know how robust it is, but in theory it should just work…. that’s the theory anyway.

    Let me know!

    Reply
  5. Hey Tom! I came across your snippet code and it works well on my site. Thank you for sharing this!

    Although I was wondering if there is a way I can present all links along the path on the breadcrumbs. Right now it’s only showing the first level and the current page. I was hoping it would show Parent > Child > Grandchild> Collection > Product. I may have missed a step somewhere. I’d very much appreciate any help on this. Thank you!

    Reply
  6. Hi Max,

    See the section above called “Limited to Three Levels.” This is a built-in limitation to Shopify. Your menu structure can only go three levels deep using the built-in Shopify menu nesting.

    You can, of course, build a menu deeper than that using straight HTML, but the Shopify menu system does not allow it and my script works only with the native Shopify menu system.

    Also, to be quite honest, if your structure goes five levels deep, you might take a step back and look again the information architecture of your site. If you have a really extensive product catalog, it might make sense, but in such cases, I’ve used things like filters and faceted search to help visitors narrow down the choices.

    Reply
  7. Hello Tom. Thank you very much for this code. It is working great for my collections and products although it does not work for a menu of just pages. Any clues as to why?

    Reply
    • Hi Ryan. I went to the domain in your email address and it seems to be working for the pages in the Resources section. Do you have a different implementation where the menu is just pages? What’s the URL?

      Reply
  8. Hi Tom

    This is brilliant thanks! I have a question though; I use a three level nav menu, the top level is based on Collections and the sub-levels are using the same top level Collections filtered with tags (I only have Collections for the top level). I’ve also changed my product-grid-item.liquid to always show the canonical URL. I notice that only the top level Collection is shown in my breadcrumb, and never includes the sub-levels. The canonical URL change I made doesn’t influence this at all.

    Any ideas on how my 2nd and 3rd levels can be shown in the breadcrumb?

    Reply
    • Hmmm… that’s an interesting question. I did not build it to use tags, but you do have access to them. There is a product.tags object just as there is a product.collection category. So basically, everywhere that I have code that iterates through a product.collections object, you would need to create similar code that iterates through the product.tags object and handle that accordingly.

      As it is now, my code only looks for collections and products, so when you have a top-level nav item that is a collection, and a second-level item that is a filter by tag on the same collection, it’s just going to see that as the collection, not the tag. So if you look in my code for the piece with the comment that says

      {%comment%} DETERMINE OBJECT TYPE - product, collection or page {%endcomment%}

      You need to also look for an object that is a collection filtered by a tag, figure out which tag is active, and then fold that into your breadcrumb. Once you do that and set the object_urls variable, the rest should work right up until the end of the code where it does the formatting. Then, once again, you’ll need to add some code to handle the “filter by tag” case.

      Where it’s going to be tough is at the second level. With Liquid, you do not have access to the URL in the address bar. In this case, the collection.tags object gives you all the tags on all the products in the current view. So if you have 20 products and some are tagged with blue and some are tagged with small and you are show products that are small and blue, the collections.tags object will hold the tags “small” and “blue” and it will be up to you to figure out what to do with that and how to decide. If your products only have one tag that appears in the navigation, you’re all set, but if you have a navigation link for small items, one for blue items and one for small blue items, I don’t know how you’ll figure that out with the information available.

      In that case, I think you would perhaps need to build your breadcrumbs after the page loads, getting the URL using Javascript and then either using more Javascript to parse the navigation or AJAX calls to get the navigation arrays.

      Reply
  9. Thanks for the detailed response Tom! My dev skills only go so far and the only reason I stuck with a top level collection structure and using tag filtering at lower levels was to ensure the URL looks correct with sublevels showing as subsites. I managed to dynamically set my Title and Description metadata for the lower levels for SEO purposes but it’s getting increasingly difficult to manage as my product range and categories grow.

    Think I need to move to Collections for all sublevels so your breadcrumb script works properly, however then the URL structure is flat with no collection nesting. I could maybe simulate a URL structure with URL redirects and manually setting the Canonical URL for each collection – some stuff to play with this weekend I guess.

    Thanks
    Mark

    Reply
  10. How embarrasing! My code got formated to html in a previous comment. I’ll just copy my comment again and add a link to github for the code in hopes of my first comment being deleted:

    I was able to implement it in a different way thanks to the tip on the new enhancements. Specifically the .active and .child_active properties. Our store menu was arranged manually and all sublevels before last were actually custom pages. I don’t know if there are any potential problems with it but the good thing about it is that it only depends on the menu structure and not on the categories of products. heres the code:
    Github link

    Reply
    • Hi BlameItOnJoe – I moved your mangled code up to the main post where it can be formatted nicer. WordPress apparently doesn’t play nice with code in comments. Thanks! I hope others will find it useful.

      Reply
  11. Hi Tom

    I’ve decided to move to Collections entirely and disband the idea of filtering lower levels using tags. I now have this scenario and issue:

    Top Level: Collection 1
    2nd Level: Collection 2
    3rd Level: Collection 3

    When clicking the first level, the breadcrumb is correct (Home > Collection 1)
    2nd level is also correct (Home > Collection 1 > Collection 2)
    However when clicking the 3rd level it’s wrong (Home > Collection 1 > Collection 3)

    Collection 2 is not shown between level 1 and 3 – any ideas?

    Thanks
    Mark

    Reply
    • Hi Mark – I don’t know. Just to ask the obvious, this is a three-level menu, with the top-level, a dropdown, and then a level that expands from the dropdown, right? In other words, I can reach the 3rd level directly from the navigation, right?

      Reply
    • Hi Mark,

      We’ve been trying to figure out the issue with the 2nd collection not showing without luck.
      I noticed you seem to have it sorted on your site now. Do you mind sharing what fixed the issue?

      I tried contacting you through the contact form on your website but I am unsure if the message went through.

      Much appreciated,
      Adam.

      Reply
      • Thanks for the head up – I guess the contact form has problems. I didn’t get that message.

        I really don’t know why the second collection (by “second” do you mean the parent or the child collection?) is not showing. Is it working correctly in your menus? That is, do you have nested menus so that it goes Parent Collection >> Child Collection >> item (i.e. a three-level menu?)

        The code itself just nests two levels deep (plus the item), but it does need a means of finding all the levels.

        Reply
        • Hi Tom,

          Thanks for the answer. The message in reference was to Mark as he has solved the issue on his site.

          It is the same issue as above:
          “When clicking the first level, the breadcrumb is correct (Home > Collection 1)
          2nd level is also correct (Home > Collection 1 > Collection 2)
          However when clicking the 3rd level it’s wrong (Home > Collection 1 > Collection 3)”

          Yes the navigation is 3 levels all in the Shopify native navigation.
          I.e Home > Collection 1 > Collection 2 > Collection 3 > Product.

          From your answer above maybe I have misunderstood that this should work?

          Reply
          • I don’t know why that is. If I have a third-level collection, I get a breadcrumb that is Home > Parent Collection > Child Collection > Grandchild Collection with the Parent and Child linked and the Grandchild (i.e. current page) unlinked. You can see it on Trail Designs. You can see under Accessories that it works for a page that is three collections deep and you can see under Stove System that it works for a page that is two collections and one product deep (i.e. a product page).

            The only thing I can tell you is no child (product or collection) can have more than one parent and have it work reliably. Somehow I would guess that your Collection 3 is a child of Collection 2 and child of Collection 1 or something like that.

          • Hi Tom,

            Thanks for your answer. We did not manage to sort this but I did find an app that solved this for us. I thought I’d just add that here in case anyone is facing the same issue as us.

            Thank you for this though Tom, a great solution but we lack the tech-know-how to make it work on our site.

            The app is found here: https://apps.shopify.com/schema-breadcrumbs
            Bogdan helped us implement it exactly where the original breadcrumbs are, couldn’t be happier.

  12. Hi Tom

    Yep, three level menu based on three collections – I built a quick test store to show the problem here: https://madebyartisans-test1.myshopify.com/ (password: teststore). You’ll see the breadcrumb issue when you click the menu and drop down to Collection Level 3.

    Just FYI, on my real store I moved from a Collection at level 1 to a Page (collection.list), with Collections at level 2 and 3 and saw the same behaviour. I’ve since removed the 3rd level for now.

    Reply
  13. Thank you for the code. For my structured data json-ld, is there a way to use sublink title for Breadcrumb item#2? I can’t figure out how. The code I am using before using linklists is

    {
    “@context”: “http://schema.org”,
    “@type”: “BreadcrumbList”,
    “itemListElement”: [{
    “@type”: “ListItem”,
    “position”: 1,
    “item”: {
    “@id”: “https://www.sample.com”,
    “name”: “Home”
    }
    },{
    “@type”: “ListItem”,
    “position”: 2,
    “item”: {
    “@id”: “/www.sample.com/collections/{{ collection.title | downcase | replace: “& “, “”| replace: ” “, “-” | remove: “‘” }}”,
    “name”: “{{ collection.title }}”
    }
    },{
    “@type”: “ListItem”,
    “position”: 3,
    “item”: {
    “@id”: “https://www.sample.com/collections/{{ collection.title | downcase | replace: “& “, “”| replace: ” “, “-” | remove: “‘” }}products/{{ product.handle | downcase | replace: “& “, “”| replace: ” “, “-” | remove: “‘”}}”,
    “name”: “{{ product.title }}”
    }
    }]
    }

    Reply
  14. Hi Tom

    The problem with the 2nd level not showing when the 3rd level is open (or when on a product at the third level) seems to be somewhere here:

    {%- for bc2_sibling_link in linklists[bc2_parent_list_handle].links -%}
    {%- assign bc2_sibling_title_handleized = bc2_sibling_link.title | handle -%}
    {% if bc2_sibling_title_handleized == bc2_list_handle %}
    {%- capture bc2 -%}{{ bc2_sibling_link.title | link_to: bc2_sibling_link.url, bc2_sibling_link.title }}{%- endcapture -%}

    It seems linklists[bc2_parent_list_handle].links is not coming back with anything. I’ve spent hours trying to find the problem, any ideas?

    The alternative method proposed by BlameItOnJoe works perfectly when 2nd and 3rd level collections are open, however shows no breadcrumb when a product is selected.

    Thanks!

    Reply
    • Sorry… I have not touched that code in years, so I honestly can’t say. You’re just going to have to put in some debugging code and see what you get. It’s a bit of a hassle since you can’t use a proper IDE with breakpoints and all that. You just have to output a bunch of HTML comments along the way and work back up the chain until you figure ou t the problem.

      Reply
    • Hello Mark,

      Looking at your site (which looks beautiful) you seem to have solved the problem with the breadcrumbs and a 3-level navigation menu. Did you use this script, or another breadcrumb-liquid

      I hope you want to share your solution, I’m still struggling with the second level

      Jeanet

      Reply
  15. Hi,
    I am brand new to coding and to Shopify but with the help of tutorials I’ve been able to create new product page templates and make design edits etc so I’m slowly getting the hang of it.

    I’m using Impulse Theme by Archetype (who sadly don’t support changes such as this), which comes with breadcrumb coding already in and with a checkbox to enable/disable “Collections” to show in the trail.

    I have a mega-menu, however, so have collections and then sub-collections (or sub-categories, whatever you’d like to call them).

    For instance, my menu reads as Shop>Bath & Body>Shampoo Bars and then within the Shampoo Bars I have different scents, such as Black Raspberry & Vanilla.

    If I look at the Black Raspberry & Vanilla Shampoo Bar Product Page, then breadcrumbs show Home / Shampoo Bars OR Home / Collections / Shampoo Bars (if I tick the collections text box available with the theme editor. I would like it to show Home / Bath & Body / Shampoo Bars.

    Will your walk through above do what I need to do? How do I determine which bits to add in to the code without disrupting the original theme code and stop it working?

    Any help you can give is most appreciated.

    Reply
    • Whether it will do what you need, I can’t say. It shouldn’t be that dependent on a theme since it just hooks into the native Shopify menu system.

      As for what you need, probably pretty much all of it. Take backup snapshots before you edit and cross your fingers! Shopify doesn’t have a staging platform to test out changes. You have to just go for it.

      Reply
  16. Hello, I used the code and it works well. Thank you so much.
    But, it creates a blank space on the home page before the hero image. I have removed the code for now because of this, but any idea if this could be fixed?

    Reply
    • I have not run the code through a validator. I would start with that and see if you have any unclosed tags or anything like that. The other thing is that Liquid sends a lot of blank lines to the browser. That is, if you have a Liquid statement in the standard delimiters (i.e. without adding the hyphen), it adds a blank line. So that might be causing some issue. Finally, I would look for float/wrap issues.

      Reply
  17. Hi Tom,

    This is so amazing, just what I’ve been looking for! I have one issue with the navigation however. I have a three level nested menu with Collection1 as parent, Collection2 as child and Collection3 as grandchild. The navigation only shows either Home > Collection1 > Collection2 > Product or Home > Collection1 > Collection3 > Product. It’s more important that the navigation shows Collection2 and Collection3 than Collection1, is there a way to work around this? I’ve tried editing the code a bit, but honestly it’s too complex for my skills.

    Hope you can help!

    Reply
    • Hi Freja,

      This script pushes Liquid to the limit. Liquid is a templating language with minimal logic and limited data structures. So that’s why there are some limitations and why, for example, going 4-deep complicates things so much in a way it simply wouldn’t in a programming language. With a relational database and a language that did recursion, this would all be a piece of cake.

      To your question, the problem is that you need a means of figuring out what the collection hierarchy is and figure out what should be at the top of the hierarchy. Since those relationships are not inherently hierarchical, there’s no simple language structure that can help you there. In other words, the grandparent collection doesn’t “know” who its children and grandchildren are as it would if it were in a relational database or a multi-dimensional array.

      What that means is that your hierarchy must be imposed manually. So there’s a few ways you could do this.

      1. You would create arrays that would list Collection 1, Collection 2 and Collection 3. That would be a maintenance nightmare. You would have to update your code every time you created a new collection.

      2. If all your Collection 1 collections are in a given menu (i.e. “Main Menu”) you could use logic similar to what I’ve done and dynamically determine all collections that are linked from Main Menu, use that to create an array. Then you would need to do that for all you Collection 3 submenus, to build an array of Collection 3 items. Then you would need to start building your breadcrumbs, check whether there are any Collection 3 items in the breadcrumb trail and, if so, build a trail without Collection 1 items. Because of the limitations of the Liquid templating language, this is boggling complex.

      So in short, you have two options:
      1. The rather complex option of making a trail that is four levels deep.
      2. The extremely complex option of figuring how how deep in your hierarchy the current page is and us that knowledge and a complete list of your hierarchical levels to decide which levels to show, and then assign those levels to the grandparent and parent levels in your script and then show the breadcrumbs.

      Both options, to my mind, are way too complex.

      Reply
      • Hello Tom, thank you for your swift reply!

        Both of those sound pretty tedious and definitely above my skill level. It will have to work is it is now, which is way better than anything I’ve ever had anyway, so thank you very much!

        I have run into another pretty damning problem however. I am running a clothes store for both men, women and children, and some of the collections are named the same thing. Whenever I have something like “Accesories” which will be in all of the parent collections, it seems the code grabs the first instance of parent in which it appears, which is in the Women’s section. So instead of the breadcrumb looking like this for a kid’s accessory Home > Kids > Accessories > Product it looks like this Home > Women > Accesories > Product. It works fine on any collections which aren’t duplicates. HAve you perhaps run into this problem before and have a quick fix? I would really like this to work, as it’s definitely the best solution for my store!

        Thanks, Freja

        Reply
  18. Hi Tom,
    I came across this artile and it was really insightful!
    I had a question regarding if it was possible to set up the product page breadcrumb based on previous collection page’s title & url.
    For example, a product belongs to two collection.
    If a user visits product through collection A, then breadcrumb will show as: HOME/collection A/product;
    If a user visits product through collection B, then breadcrumb will show as: HOME/collection B/product.
    Any idea on how this can be accomplished?
    Best,
    Vincnent

    Reply
  19. Thank you for this! It doesn’t quite follow the path taken on the main menu. Sometimes, if a product is in more than one collection, it will show the end path of the OTHER collection, not the path the person took to get there. SO they cannot backtrack one to get to the previous item anymore. Is there a way to make sure the breadcrumbs show the path actually taken?

    Reply
  20. Hi Vincent and Yvette.

    No, what you want this to do is not possible with this script. That would require an entirely different approach and a completely different script. Almost no overlap at all.

    In modern web parlance, the term “breadcrumb” is rather a misnomer. It usually means, as it does here, a means of locating the user within the hierarchy of the site. It does not mean, as it did etymologically, keeping a record of the user’s path through the site.

    Web pages are, by default, “stateless” meaning that you don’t know what the person has done before. To change that, you need to implement some sort of tracking of user behavior. The common way would be to use cookies. You could, in theory, store the user journey in a cookie and retrieve the path to the current page by the user. This is a completely different thing though.

    One way to think of it is that my script is designed to show users where they are in the site hierarchy and organization. It has nothing to do with where they have been. If they do a Mojeek search and land on a given page, the breadcrumbs orient them to where they are on the site.

    With your system, the person who does a Mojeek search and lands on a page would get no breadcrumb because that person is at the beginning of her journey. It would then add pages to the trail as the user navigates, but would give the user no indication of where they are in the site and how the site is organized.

    Numerous usability studies have shown that one of the most universal bits of knowledge web users have is how to use the back button of their browser. They also have the History in their browser, though far fewer know how to do that.

    Typically for Shopify, what you would want to do to achieve what you want is to add a “Recently Viewed” plugin. This is how most websites do it: breadcrumbs for site organization and “recently viewed” for products recently viewed.

    Reply
  21. Hi Tom,

    Thanks so much for this amazing code!

    We have Home > Collection Menu > Collection > Product

    Collection Menu isn’t a page for us but it’s in the breadcrumbs as #. Is there a way we can remove that level from the breadcrumbs?

    Reply
  22. The code works very well for the main language, but I also have a second language in which the code no longer works so smoothly. It does not show breadcrumbs or not at all, or some strange type of Special Offers, where a given product is added to the vitamin category and in the ENG version it shows them correctly.

    Is there any way to fix this?

    Reply
  23. Hi Tom,

    Thanks for this brilliant code!
    I’m wondering whether it’d be possible to remove a section, so instead of:
    Home > Main Category > Sub Category > Product
    You had:
    Home > Sub Category > Product
    Thanks!

    Reply
  24. This worked but I got “translation missing: en.general.breadcrumbs.home” in the breadcrumb link. How do I fix this?

    Reply
  25. Sorry guys – it seems my comment notifications have not been working for quite a while.

    Daisy –
    You would just have to remove one level of nesting. It’s cumbersome, given the limitations of Liquid, but getting rid of one level and simply having parent and child would make it all much simpler. That just wasn’t my use case.

    Krzysztof –
    Sorry, but I just don’t know much about localization in Shopify/Liquid. I’ve dealt with it a fair bit in Drupal, but in Shopify, all sites have been monolingual. In general, though, you have a URL schema for localization in Shopify and you can get that with the routes objects
    https://shopify.dev/api/liquid/objects#routes
    You would have to include that in your logic and find the corresponding menus. At least that’s where I would start. I have not played with that at all.

    Tal – this is a fairly common issue. Start with the simple writeup at Little Stream Software
    https://www.littlestreamsoftware.com/articles/translation-missing-keeps-showing-up-on-my-shopify-store/

    Also, consult the docs on theme translation
    https://help.shopify.com/en/manual/online-store/themes/customizing-themes/language/translate-theme

    And the Shopify forums, for example
    https://community.shopify.com/c/shopify-design/pages-showing-quot-translation-missing-en-products-general-sold/m-p/523116

    Fixing this is not something you would do with this particular script. It has to do with missing translation strings on your site that perhaps were not revealed earlier because you were not trying to access the breadcrumb string, so you did not get an error when that string turned out to be empty.

    Reply
  26. Hi – This is awesome! Thank you!

    However, we have lots of menus and it seems like some collections work well, others don’t pick up a parent collection at all, and others pick up a parent collection from one of the many menus we have (not necessarily something we wanted customer-facing). Is there any way to set a menu as the default “breadcrumb menu” so that it always chooses the hierarchy we’ve set?

    Reply
  27. Hello Tom,

    This is great peace of code and still popular. But I’m struggling with the 3-level menus too.
    My menu has both 2-level and 3-level parts … the 2-level menu is working perfect.
    On the 3-level menu the breadcrumb of the second level is missing on both the collection page and the product page.

    In this line it’s going wrong:
    {%- for bc2_sibling_link in linklists[bc2_parent_list_handle].links -%}
    Both “bc2_sibling_link” and “linklists[bc2_parent_list_handle].links” are empty at this point and I don’t understand why. “bc2_parent_list_handle” has a value.

    Do you have a clue? Because I do not …

    Best regards
    Jeanet

    Reply
        • Hmm… does the product page have a separate template in your theme?

          In other words, is the liquid file in your theme that controls the breadcrumbs universal?

          Reply
          • I’m using Dawn theme .. but my description was not completely accurate.

            The render ‘breadcrumbs’ is in the theme.liquid:

            {% render ‘breadcrumbs’ %}
            {{ content_for_layout }}

            The script BlameitonJoe works fine on collection pages of all menu-levels (max.3). On the product-page it shows only the ‘Home’ link in the breadcrumb and no collections nor product title.

            A combination of both scripts would be perfect, but unfortunateley i’m not able to create it 🙁

  28. I stumbled upon this blog post when trying to implement breadcrumbs in Shopify 2.0 themes and it was extremely helpful. I will be using a lot of the logic provided by this post for a commercial Shopify Theme Project, so I wanted to add to this post with a helpful comment.

    There have been quite a few comments asking for a liquid snippet that combines the logic from this post with BlameItOnJoe’s elegant approach to creating breadcrumbs.

    So I’ve done just that in my version of Shopify breadcrumbs.
    https://github.com/pierrewebdev/shopify-breadcrumbs-based-on-menu/blob/main/breadcrumbs.liquid

    I have tested this on the Shopify Refresh theme and Dawn theme and it works for both.
    I noticed that all the complex logic used in this post only really matters when dealing with the collection and product page template. And since BlameItOnJoe’s approach works really well for everything that is not a product page, I used his snippet in a case statement to cover every template that is not a product template.

    On product templates, I used the logic provided by the author of this post.
    Hope this helps someone!

    Reply
    • Thanks Patrick! I just got in from traveling, but once I have a minute, I’ll update the main post to link to your code on github. It’s unlikely people will find it in the comments and, much as I appreciate that people have found the code useful, I don’t have much interest in maintaining it.

      Reply
    • Hello Patrick!

      I used that code, lovely, thank you so much. Work well, but for me missing the path from product category. Now, showing the path only on item side. Could you help me please?

      Reply
  29. Thank you very much. All. I am not an IT but I spent 5 hours between this incredible article and and my Web site. Finally, I got the wanted result.
    I Sell handcrafts and I handcraft my website ahhhh.

    The last post is amazing Just copy and past with the header of the original code.

    Atef

    Reply
  30. Thanks for the code, from a Shopify user that generally gets all of my DIY stuff from wonderful coding folks like yourself 🙂

    I’m so close, but am getting this error before the breadcrumbs:
    translation missing: en.general.breadcrumb.home › Shop › Velvet › Jeweltone Solids Tri-Split Velvet

    My snippet is currently breadcrumb.liquid. Theme is Crave and this is on a copy so you won’t see it live.

    Current live theme has different code and will only show two levels (no nested breadcrumbs).

    Would appreciate any further help you may have to fix it.

    Thanks!
    Misty

    Reply
  31. This is brilliant Tom. It’s the first time I have an issue with a Shopify topic and the working solution is NOT a chargeable app. And the code provided is qualitative and you share it in a very humble way by also suggesting an alternative from another contributor.

    I still would like to fine-tune your code to my context, maybe you can help. Some of my products appear in several collections that are attached to menu categories. Example:
    a pair of baby boy slippers appear in 2 collections.
    A. “New-born Baby Boy collection” and
    B. “Baby Boy Slippers collection”

    When landing on the product page of this product, the breadcrumb shows:
    Baby Boy > New-born Baby Boy Collection > Product Name

    Although I would like to show
    Baby Boy > Baby Boy Slippers collection > Product Name

    Is there a way to force it to a default collection Baby Boy Slippers Collection that i would map inside a product maybe using a metafield or smth more clever ?

    Thanks so much

    Reply
    • Sorry, I missed this. There’s no obvious way to do this. You would need some way to make a collection be the “navigation” collection vs other collections. I can think of ways you could do it like, for example, have a product tag that identifies which level 2 collection to use for nav, but that is going to be a nightmare. Alternatively, you could have a perfectly siloed set of collections that map to navigation and those other collections would become product tags or something.

      Reply
  32. Hi there, the solution is great but unfortunately, I faced an issue when we had collections under two different menu links. In this case, the breadcrumbs are not showing correctly.
    for example:
    Test > test 1 > test 2 > test 3 > test 2 > test >3
    but it should be Test > test 1 > test 2 > test 3
    and Test > test 2 > test >3

    Reply
    • As I explain, one condition of using this is that there is a single menu path to the item. Otherwise, you have race conditions and duplications that will yield unpredictable results. Avoiding that would result in adding a lot of logic to the code and would weigh it down in my use case.

      In any event, this code is very very old. Shopify has moved on since I wrote this. The better solution is simply to get a modern theme that uses all the modern Shopify features and includes breadcrumbs.

      Reply

Leave a Reply to Daisy Cancel reply