12.4 Lookup for Taxonomy Term Pages
Right, so you’ve got a taxonomy. Maybe it’s “categories” for your blog posts, or “projects” for your portfolio. You’ve dutifully tagged your content, and now you want a page that lists all the terms within that taxonomy (e.g., a page listing all your categories, like “Technology”, “Cooking”, “Rants About Sock Disappearance”). Then, for each term, you want a page showing all the content associated with it (e.g., all posts in the “Technology” category). This is where Hugo’s template lookup logic goes from “sensible” to “slightly arcane, but powerful once you get it.” Let’s break down how it finds the templates for those term pages.
First, a quick terminology check. A taxonomy is a grouping, like “categories” or “tags”. A term is a value within that taxonomy, like “golang”. A term page is the page that lists all the content for a specific term.
The Default Lookup Order: The Logical Path
Hugo, being the thoughtful but occasionally over-enthusiastic framework it is, tries a whole bunch of files in a specific order until it finds one that exists. For a taxonomy term page (say, for the “golang” term in your “categories” taxonomy), it looks for templates in this sequence:
/layouts/taxonomy/<TERM>.html→ e.g.,layouts/taxonomy/golang.html/layouts/taxonomy/<TAXONOMY>.html→ e.g.,layouts/taxonomy/categories.html/layouts/taxonomy/list.html/layouts/_default/<TERM>.html→ e.g.,layouts/_default/golang.html/layouts/_default/taxonomy.html/layouts/_default/list.html
Let’s be honest, number 1 (taxonomy/golang.html) is a bit silly for most use cases. You’d have to create a unique template for every single tag and category you ever use? That way lies madness. Its existence is a testament to Hugo’s flexibility, not its practicality. You’ll almost never use it.
The real workhorses are {{< bibleref “Numbers 2
” >}} and 3. You’ll typically create a layouts/taxonomy/categories.html to style all your category term pages, and a layouts/taxonomy/tags.html for your tags. If Hugo can’t find a taxonomy-specific template, it falls back to the sensible defaults: taxonomy.html and finally the catch-all list.html.
Here’s a bare-bones example of what your layouts/taxonomy/categories.html might look like. This is where you learn the .Title for a term page is the term itself, which is brilliantly straightforward.
<!-- layouts/taxonomy/categories.html -->
<h1>All posts in the "{{ .Title }}" category</h1>
<ul>
{{ range .Pages }}
<li><a href="{{ .Permalink }}">{{ .Title }}</a></li>
{{ end }}
</ul>
The Secret Weapon: Section-Specific Overrides
Here’s where Hugo gets clever and where most people’s minds get briefly blown. Let’s say you have a section called blog and a taxonomy called categories. What if you want the “golang” category page for your blog posts to look completely different from the “golang” category page for your, say, book-reviews section?
Hugo has a hidden ladder of lookups for this, inserting section-specific templates before the default ones. The full, no-stone-left-unturned lookup order for our “golang” category term in the “blog” section is actually:
/layouts/taxonomy/blog/<TERM>.html→taxonomy/blog/golang.html/layouts/taxonomy/blog/<TAXONOMY>.html→taxonomy/blog/categories.html/layouts/taxonomy/<TERM>.html→taxonomy/golang.html(still silly)/layouts/taxonomy/<TAXONOMY>.html→taxonomy/categories.html(the common one)/layouts/taxonomy/list.html/layouts/_default/<TERM>.html→_default/golang.html/layouts/_default/taxonomy.html/layouts/_default/list.html
See that? Steps 1 and 2. You can create a template at layouts/taxonomy/blog/categories.html that will only be used for category term pages when the content belongs to the blog section. The content’s original section becomes part of the lookup path. This is insanely powerful for large, complex sites with different content types.
<!-- layouts/taxonomy/blog/categories.html -->
<h1>Blog Posts about {{ .Title }}</h1>
<p>Here are our finely crafted technical writings.</p>
{{ range .Pages }}
<!-- your blog-specific styling here -->
{{ end }}
<!-- layouts/taxonomy/book-reviews/categories.html -->
<h1>Book Reviews for {{ .Title }}</h1>
<p>Here are our opinions on books.</p>
{{ range .Pages }}
<!-- your book-review-specific styling here -->
{{ end }}
Best Practices and The “Oh, Right” Pitfall
The biggest pitfall is forgetting that the .Pages in a term template are the pages with that term, not the terms themselves. To get a list of all terms in a taxonomy (e.g., a cloud of all your tags), you need to use the .Site.Taxonomies variable, and that’s a different template altogether (/layouts/taxonomy/list.html is not for that).
The best practice? Keep it simple to start.
- Create a
layouts/taxonomy/tags.htmland alayouts/taxonomy/categories.htmlfor your global styling. - Only venture into the section-specific overrides (
taxonomy/blog/categories.html) when you have a genuine, pressing need for a different design. It’s a fantastic feature, but don’t add complexity prematurely. - Remember that
_default/list.htmlis your site’s final fallback for any list page, so if you make a change there, it affects homepages, section lists, and taxonomy pages. Be specific when you can to avoid unintended consequences.
Ultimately, this system is a testament to Hugo’s “content-first” philosophy. The structure of your content directly influences the design choices available to you, which is how it should be. It’s just a matter of learning the (admittedly numerous) paths it takes to find those designs.