Add a Table of Contents to your Shopify Blog without App or Plug-in

As you dive deeper into creating and managing your own Shopify blog, you might be seeking efficient ways to structure your articles and provide easy navigation for your readers. One solution is adding a Table of Contents (ToC) to your Shopify posts. However, some may think that it requires the help of an app or plug-in, which might come with additional costs or compatibility issues.

Today, I want to reveal that this doesn’t have to be the case. If you have any coding experience, you should be able to follow along with the code below. If you have no coding experience, you can try to follow along with our steps, if your theme files looks similar to our, you should be able to copy and paste the code in the correct areas of your files to replicate the ToC.

Finally, you can also hire someone on Fiverr with coding experience that can help you quickly set up the code in your theme.

Why Create a Table of Contents in Shopify?

Before we start, let’s quickly cover why a Table of Contents can be a valuable addition to your Shopify blog.

  1. Enhanced User Experience: A ToC makes it easier for your readers to navigate through your articles, especially when they are long and detailed.
  2. Improved SEO: Google and other search engines favor well-structured content. A ToC can help them understand and index your content better, leading to potential improvements in your search engine rankings.
  3. Increased Time On Page: With easier navigation, readers are more likely to stick around, improving your site’s “dwell time,” another factor that could boost your SEO.

How to Add a Table of Contents to your Shopify Blog without Apps or Plug-ins

The most cost-effective and automated option to add a ToC is to create a script in the article__main.liquid file of your Shopify theme. For this tutorial, we are using the Flex Theme by ‘Out of the Sandbox’, but the process may slightly differ depending on the theme you are using. It’s entirely automatic and completely free. However, it does require some coding experience.

How it works

This automated ToC works like this:

  1. I write a blog with the correct structure (H2 tags for headings, H3 for subheadings, H4 for sub-subheading and so-on).
  2. In the HTML editor in Shopify I add the tag ‘<!– split –>‘ right after the introduction paragraph.
    • This allows me to split the blog article to inject the table of contents right after the introduction paragraph or where ever I choose to add the ‘<!– split –>‘ tag in the HTML.
    • If you want the table of contents to show up at the top of every blog post before the introduction paragraph – you can avoid this step. This is something that I wanted to control because I like to have my introduction with keyword phrases before the actual ToC.
    • You can learn more about the ‘split’ method to split the information in Shopify pages here.

How to add the code to Shopify theme

Let’s walk through how to do this step by step:

Step 1: Accessing your Shopify Theme Code

  • Login to your Shopify store’s admin panel.
  • Navigate to “Online Store” > “Themes”.
  • Click on “Actions” for the theme you want to edit, then click “Edit code”.

Step 2: Locate the article__main.liquid file

  • In the theme code editor, locate the article__main.liquid file. For Flex Theme, it is located under “Sections”. However, your theme might store this main article layout in a different file, so make sure to check your theme’s documentation or ask the theme’s support if you have trouble finding it.

Step 3: Creating the ToC Javascript Code

Now, this is the crucial part where you need to inject the Table of Contents script. (Note: Make sure to back up your theme before making any code changes.) Luckily Shopify automatically creates backups as well so you can always revert back to the original code if something goes wrong.

You will be adding some vanilla Javascript code to loop through the DOM and list out all of the H tags in an ordered list, along with adding a custom ID for each of the H tags to do internal linking,

The highlighted parts below need to be added to the <div> with the class “container article-content has-padding-top has-padding-bottom”. The parts not highlighted are part of the original code.

Code Part 1:

  <div id="MainContent" class="container article-content has-padding-top has-padding-bottom">
    <div class="one-whole column content">
      {% if article.image and section.settings.show_featured_image %}
        <p>
          {% render 'image-element',
                  image: article.image,
                  alt: article.image.alt,
                  stretch_width: true
          %}
        </p>
      {% endif %}

  {% assign my_description = article.content | split: '<!-- split -->'  %}

{{ my_description[0] }}
  
  <div class="page-width page-width--narrow"> <div id="toc"> <h1 class="h1">Table of Contents</h1> 
 
  <nav class="table-of-contents">
  </nav>
  <article class="article">


  </article>
  
  </div> </div>
  
{{ my_description[1] }}
  
    </div>
  </div >

The code below is the whole JS script and CSS script that needs to be added to the articles__main.liquid file. There are several notes in the code to help you to follow along.

You can make edits to the CSS depending on your website branding.

Code Part 2:

<script>

class TableOfContents {
    /*
        The parameters from and to must be Element objects in the DOM.
    */
    constructor({ from, to }) {
        this.fromElement = from;
        this.toElement = to;
        // Get all the ordered headings.
        this.headingElements = this.fromElement.querySelectorAll("#MainContent h2, h3");
        this.tocElement = document.createElement("div");
    }

    /*
        Get the most important heading level.
        For example if the article has only <h2>, <h3> and <h4> tags
        this method will return 2.
    */
    getMostImportantHeadingLevel() {
        let mostImportantHeadingLevel = 6; // <h6> heading level
        for (let i = 0; i < this.headingElements.length; i++) {
            let headingLevel = TableOfContents.getHeadingLevel(this.headingElements[i]);
            mostImportantHeadingLevel = (headingLevel < mostImportantHeadingLevel) ?
                headingLevel : mostImportantHeadingLevel;
        }
        return mostImportantHeadingLevel;
    }

    /*
        Generate a unique id string for the heading from its text content.
    */
    static generateId(headingElement) {
        return headingElement.textContent.replace(/\s+/g, "_");
    }

    /*
        Convert <h1> to 1 … <h6> to 6.
    */
    static getHeadingLevel(headingElement) {
        switch (headingElement.tagName.toLowerCase()) {
            case "h1": return 1;
            case "h2": return 2;
            case "h3": return 3;
            case "h4": return 4;
            case "h5": return 5;
            case "h6": return 6;
            default: return 1;
        }
    }

    generateToc() {
        let currentLevel = this.getMostImportantHeadingLevel() - 1,
            currentElement = this.tocElement;

        for (let i = 0; i < this.headingElements.length; i++) {
            let headingElement = this.headingElements[i],
                headingLevel = TableOfContents.getHeadingLevel(headingElement),
                headingLevelDifference = headingLevel - currentLevel,
                linkElement = document.createElement("a");

            if (!headingElement.id) {
                headingElement.id = TableOfContents.generateId(headingElement);
            }
            linkElement.href = `#${headingElement.id}`;
            linkElement.textContent = headingElement.textContent;

            if (headingLevelDifference > 0) {
                // Go down the DOM by adding list elements.
                for (let j = 0; j < headingLevelDifference; j++) {
                    let listElement = document.createElement("ol"),
                        listItemElement = document.createElement("li");
                    listElement.appendChild(listItemElement);
                    currentElement.appendChild(listElement);
                    currentElement = listItemElement;
                }
                currentElement.appendChild(linkElement);
            } else {
                // Go up the DOM.
                for (let j = 0; j < -headingLevelDifference; j++) {
                    currentElement = currentElement.parentNode.parentNode;
                }
                let listItemElement = document.createElement("li");
                listItemElement.appendChild(linkElement);
                currentElement.parentNode.appendChild(listItemElement);
                currentElement = listItemElement;
            }

            currentLevel = headingLevel;
        }

        this.toElement.appendChild(this.tocElement.firstChild);
    }
}

document.addEventListener("DOMContentLoaded", () =>
    new TableOfContents({
        from: document.querySelector(".article"),
        to: document.querySelector(".table-of-contents")
    }).generateToc()
);



</script>

<style>

h2 {
  scroll-margin-top: 5rem; /* whatever is a nice number that gets you past the header */
}

.table-of-contents ol {
    list-style: none;
    padding: 0;
    counter-reset: counter-table-of-contents;
}

.table-of-contents ol ol {
    padding-left: 2em;
}

.table-of-contents ol li {
    margin: .5em 0;
}


.page-width.page-width--narrow {
    position: relative;
    border: 1px solid #b3b3b3;
    padding: 15px 30px 15px 30px;
    margin-bottom: 35px;
    width: auto;
    background: #f5f5f5;
    border-radius: 10px;
}
html { scroll-behavior: smooth; }
.table-of-contents ol ol {
    padding-left: 0em;
    margin-bottom: 20px;
}

  </style>

That’s it! That’s all the code that is needed to be added to your theme.

Now you can write a blog and inject <!– split –> in the HTML where you want the ToC to be generated.