Why add a table of contents?
Two reasons, one for readers and one for Google.
Readers of a long post came for one part of it. A linked list at the top lets them jump there instead of scrolling, and a reader who finds their answer in ten seconds is more likely to read the rest, click a product, or come back. On a phone, where a 2,000-word guide is forty screens, this matters more.
Google sometimes shows links to sections of a page under a search result (“Jump to”). Its sitelinks guidance asks for informative headings and concise, relevant link text (Google on sitelinks), and a table of contents is exactly that: headings with anchors and in-page links that point to them. It doesn’t rank the post, but it can make the result bigger and send people to the right section.
What it won’t do: fix a thin post. A table of contents on three short paragraphs is a list of three links above a page that fits on one screen. We’d leave it off anything under about 800 words.
Three ways to add one, in order
1. By hand, in the blog editor
Best for one or two long posts, or to see whether readers use it before you set up anything.
Shopify’s rich text editor can insert links and lets you edit the HTML (Shopify Help Center). The idea: give each heading an id, then put a list of links to those ids at the top.
- Open the post, click the
<>button to switch the editor to HTML. - Find each heading and add an id:
<h2 id="choose-a-theme">Choose a theme</h2>. Use lowercase, hyphens, no spaces. - At the top of the post, add the list:
<nav class="toc" aria-label="Table of contents">
<p><strong>In this guide</strong></p>
<ol>
<li><a href="#choose-a-theme">Choose a theme</a></li>
<li><a href="#set-up-collections">Set up collections</a></li>
<li><a href="#write-product-pages">Write product pages</a></li>
</ol>
</nav>- Switch back to the visual editor and save. Click each link in the preview.
Limits: every new heading means editing the list again, and the editor sometimes strips ids when you reformat a heading. It also looks the way your theme styles any list, which is usually fine and sometimes ugly. For a blog you post to weekly, go to option 2 or 3.
2. A script in the blog post template
Best for stores with theme access and a developer, or a merchant who doesn’t mind the code editor. Free, and it works on every post from now on.
The script runs on the article page, finds the headings in the post, gives each an id, and builds the list. On an Online Store 2.0 theme (Dawn and everything since) you add it as a Custom Liquid section, so it survives theme updates.
- In the admin, open Online Store › Themes › Customize, then switch the page selector to Blog posts › Default blog post.
- Click Add section › Custom Liquid. Dawn’s blog post section has no Custom Liquid block, so the code sits in its own section below it; the script moves the list to the top of the article text.
- Paste this into the Custom Liquid field:
<nav class="toc" aria-label="Table of contents" hidden>
<p class="toc__title">In this article</p>
<ol class="toc__list"></ol>
</nav>
<script>
(function () {
var article = document.querySelector('.article-template__content, .rte');
var nav = document.querySelector('.toc');
if (!article || !nav) return;
var headings = article.querySelectorAll('h2, h3');
if (headings.length < 3) return;
var list = nav.querySelector('.toc__list');
headings.forEach(function (h, i) {
if (!h.id) {
h.id = h.textContent.trim().toLowerCase()
.replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'section-' + i;
}
var li = document.createElement('li');
li.className = 'toc__item toc__item--' + h.tagName.toLowerCase();
var a = document.createElement('a');
a.href = '#' + h.id;
a.textContent = h.textContent;
li.appendChild(a);
list.appendChild(li);
});
article.insertBefore(nav, article.firstChild);
nav.hidden = false;
})();
</script>
<style>
.toc { border: 1px solid currentColor; padding: 1rem 1.25rem; margin: 0 0 2rem; }
.toc__title { margin: 0 0 .5rem; font-weight: 600; }
.toc__list { margin: 0; padding-left: 1.25rem; }
.toc__item--h3 { margin-left: 1rem; }
</style>- Save, open a post with at least three headings, and check the links. If the list doesn’t show, your theme’s article content uses a different class: right-click the post text, choose Inspect, and replace
.article-template__content, .rtewith the class you see.
Three things the script does on purpose: it only builds a list when there are three or more headings, so short posts stay clean; it keeps ids you set by hand; and it marks H3s so you can indent them.
Limits: it’s JavaScript, so the list isn’t in the HTML Google first downloads. Google renders JavaScript and the anchors are on the headings either way, but if you want the list in the source, option 1 or an app that renders through Liquid is the way. On a vintage (pre-2.0) theme there’s no Custom Liquid section; paste the same code into sections/article-template.liquid above the content and accept that a theme update may remove it. And every style change is a code change.
3. An app
Best for anyone who wants it on every blog post, and on product, collection or store pages too, with styling in the theme editor and no code.
A table of contents app is a theme app block: you add it in the theme editor, pick colors, numbering, position and which heading levels count, and it builds the list on every page of that type. Free plans usually cover blog posts; product and collection pages are paid.
Ours is Auto TOC: Blog SEO & Nav: free on blog posts, $1.99/month or $39.90 once for products, collections and pages, with a pick of heading levels H1 to H6, show on selected articles or all, and support for Online Store 2.0 and vintage themes. We compared it with four others, two of which have mobile features ours doesn’t, in best table of contents apps for Shopify. Read that before you pick; the honest answer depends on how your readers arrive.
Step by step: what every table of contents needs
Whichever option you choose, check these on one post before rolling it out:
- Headings are real headings. The editor lets you make text bold and large; that’s not an H2. Use the paragraph style menu so the post has H2s for sections and H3s under them. No table of contents can read bold text.
- One H1, which is the title. The theme sets it. Don’t add another in the post.
- Anchors land below a fixed header. If your theme has a sticky header, a jump lands under it. Add
html { scroll-padding-top: 80px; }(your header’s height) in the theme’s CSS, or in the<style>block above. - The list is near the top, after the intro. Readers want the quick answer first, then the map.
- Phone check. Open the post on a phone. If the list is longer than the screen, cut it to H2s only, or use a collapsible one.
- Click every link. Headings with the same text get the same id and only the first works. Rename one of them.
Mistakes we see most
- A list with one or two items. Needs at least three to be worth the space. The script above enforces it; check whether your app has a minimum setting.
- Every H4 included. Twenty links is a wall. H2s, maybe H3s, nothing deeper.
- Headings written for the list, not the reader. “Step 3” tells nobody anything in a table of contents. “Submit the sitemap in Search Console” does.
- Sticky header hides the heading after a jump. See step 3 above; it’s one CSS line and most stores never add it.
- Two tables of contents. A theme that already has one (some blog-focused themes do) plus an app gives two. Check the article template before you install anything.
- Expecting a ranking jump. You’ll get jump links in results and better engagement; you won’t move from page 3 to page 1 because of a list.