[Danny Berger](https://dpb587.me/ "Home")

# Nested Taxonomies with Hugo

March 9, 2020

I was looking into a [WordPress](https://wordpress.org/) to [Hugo](https://gohugo.io/) site migration recently. One problem I noticed was around Hugo's incomplete support for hierarchical taxonomies - something that WordPress offers with its notion of [categories](https://make.wordpress.org/support/user-manual/content/categories-and-tags/categories/). I spent a while trying to adapt the built-in [taxonomy conventions](https://gohugo.io/content-management/taxonomies/), but eventually I decided it wasn't practical. Instead, I switched to using regular content [sections](https://gohugo.io/content-management/sections/) and some extra templating magic to support these more complex taxonomies. The following are general strategies that I found to work for me.

# Starting with Content

The first type in this example is the taxonomy-equivalent type which I call a _collection_. To maintain the hierarchy, each collection must be its own section. For example, the following represents the _Confections_ category (which itself is a subcategory of _Sweets_):

[content/collection/sweets/confections/_index.md](https://github.com/dpb587/dpb587.me/blob/main/appendix/2020-03-09-nested-taxonomies-with-hugo/content/collection/sweets/confections/_index.md)

```
---
title: "Confections"
---

Desserts, candies, and sweet breads

```

The next type is a _product_, and these are regular pages which can include a list of the collections they belong to. For example:

[content/product/schoggi-schokolade.md](https://github.com/dpb587/dpb587.me/blob/main/appendix/2020-03-09-nested-taxonomies-with-hugo/content/product/schoggi-schokolade.md)

```
---
title: "Schoggi Schokolade"
brand: "heli-susswaren-gmbh-co-kg"
collections:
- "sweets/confections"
- "sales/2020-11"
---

100 - 100 g pieces

```

# Listing Pages

When it comes to rendering collections, I primarily use the `section.html` template. One of the traditional views is to show all the products in the collection. For this, a straightforward [`where` function](https://gohugo.io/functions/where/) with an `intersect` can be used:

[layouts/collection/section.html](https://github.com/dpb587/dpb587.me/blob/main/appendix/2020-03-09-nested-taxonomies-with-hugo/layouts/collection/section.html#L11-L13)

```
<dt>Products</dt>
{{- range where .Site.Pages ".Params.collections" "intersect" ( slice ( .File.Path | strings.TrimPrefix ( printf "%s/" .Type ) | strings.TrimSuffix "/_index.md" ) ) }}
  <dd><a href="{{ .Permalink }}">{{ .Title }}</a></dd>

```

The result for a collection with two products then looks something like:

[public/collection/sweets/confections/index.html](https://github.com/dpb587/dpb587.me/blob/main/appendix/2020-03-09-nested-taxonomies-with-hugo/public/collection/sweets/confections/index.html#L56-L58)

```
<dt>Products</dt>
  <dd><a href="/product/schoggi-schokolade/">Schoggi Schokolade</a></dd>
  <dd><a href="/product/teatime-chocolate-biscuits/">Teatime Chocolate Biscuits</a></dd>

```

# Listing Nested Pages

A more complicated view (which I was not able to reproduce with built-in Hugo taxonomies) was to list all items in this taxonomy collection _and_ sub-collections. To support this, I created a template helper function to recursively capture all the sub-collections into a [`.Scratch` variable](https://gohugo.io/functions/scratch/):

[layouts/_default/baseof.html](https://github.com/dpb587/dpb587.me/blob/main/appendix/2020-03-09-nested-taxonomies-with-hugo/layouts/_default/baseof.html#L59-L64)

```
{{ define "_nested_sections_slice" -}}
  {{ $.Scratch.Add "nested_sections_slice" ( slice ( .Section.File.Path | strings.TrimPrefix ( printf "%s/" .Section.Type ) | strings.TrimSuffix "/_index.md" ) ) -}}
  {{ range .Section.Sections -}}
    {{ template "_nested_sections_slice" ( dict "Scratch" $.Scratch "Section" . ) -}}
  {{ end -}}
{{ end -}}

```

Then, after executing it, I use it with another `where`/`intersect` lookup (and I could add [`Paginate`](https://gohugo.io/templates/pagination/) for long lists):

[layouts/collection/section.html](https://github.com/dpb587/dpb587.me/blob/main/appendix/2020-03-09-nested-taxonomies-with-hugo/layouts/collection/section.html#L18-L24)

```
<dt>Nested Products</dt>
{{- template "_nested_sections_slice" ( dict "Scratch" .Scratch "Section" . ) }}
{{- range where .Site.Pages ".Params.collections" "intersect" ( $.Scratch.Get "nested_sections_slice" ) }}
  <dd><a href="{{ .Permalink }}">{{ .Title }}</a></dd>
{{- else }}
  <dd>none</dd>
{{- end }}

```

The result for a collection containing two sub-collections with three total products then looks like:

[public/collection/sweets/index.html](https://github.com/dpb587/dpb587.me/blob/main/appendix/2020-03-09-nested-taxonomies-with-hugo/public/collection/sweets/index.html#L55-L58)

```
<dt>Nested Products</dt>
  <dd><a href="/product/grandmas-boysenberry-spread/">Grandma&#39;s Boysenberry Spread</a></dd>
  <dd><a href="/product/schoggi-schokolade/">Schoggi Schokolade</a></dd>
  <dd><a href="/product/teatime-chocolate-biscuits/">Teatime Chocolate Biscuits</a></dd>

```

# Page Navigation

For the product pages, a common view is to show breadcrumbs for the taxonomy collections it is in. To help with that, I created a template function that I can call with a collection to show the hierarchy:

[layouts/_default/baseof.html](https://github.com/dpb587/dpb587.me/blob/main/appendix/2020-03-09-nested-taxonomies-with-hugo/layouts/_default/baseof.html#L53-L57)

```
{{ define "taxonomy-hierarchy" }}
  {{- if and .Parent ( eq .Parent.Type .Type ) }}
    {{- template "taxonomy-hierarchy" .Parent }} / {{ end -}}
  <a href="{{ .Permalink }}">{{ .Title }}</a>
{{- end }}

```

Then, on the individual product pages, I can show a set of breadcrumbs for each collection. I use the [`.GetPage`](https://gohugo.io/functions/getpage/) function to load the collection before passing it to the template function (with [`errorf`](https://gohugo.io/functions/errorf/) helping to avoid frontmatter typos):

[layouts/product/single.html](https://github.com/dpb587/dpb587.me/blob/main/appendix/2020-03-09-nested-taxonomies-with-hugo/layouts/product/single.html#L4-L11)

```
<dt>Collections</dt>
{{- range .Params.collections }}
  {{- with $.Site.GetPage ( printf "collection/%s" . ) }}
    <dd>{{ template "taxonomy-hierarchy" . }}</dd>
  {{- else }}
    {{ errorf "unable to find collection: %s" . }}
  {{- end }}
{{- end }}

```

The result for a product in the _Confections_ and _On Sale_ collections then looks like:

[public/product/schoggi-schokolade/index.html](https://github.com/dpb587/dpb587.me/blob/main/appendix/2020-03-09-nested-taxonomies-with-hugo/public/product/schoggi-schokolade/index.html#L48-L50)

```
<dt>Collections</dt>
    <dd><a href="/collection/sweets/">Sweets</a> / <a href="/collection/sweets/confections/">Confections</a></dd>
    <dd><a href="/collection/sales/">On Sale</a> / <a href="/collection/sales/2020-11/">This Week&#39;s Deals</a></dd>

```

# Still, a taxonomy

One last thing: in order to avoid confusion of the `collection/` root directory being a collection itself, I explicitly give it a type of `taxonomy`. There's nothing magic about the name `taxonomy` here – it is just another content type – but it does help simplify some of the earlier template functions and make it easier to have a different theme template at that path.

[content/collection/_index.md](https://github.com/dpb587/dpb587.me/blob/main/appendix/2020-03-09-nested-taxonomies-with-hugo/content/collection/_index.md)

```
---
type: "taxonomy"
title: "Collections"
---

```

---

_Note: the sample site for examples from this post is [available](https://github.com/dpb587/dpb587.me/tree/master/appendix/2020-03-09-nested-taxonomies-with-hugo) if you want more details or to run `hugo serve` yourself._

## Reader Comments

Copyright © 2026 // [dpb587.me](https://dpb587.me/) is a [personal](https://dpb587.me/projects/website), [open source](https://github.com/dpb587/dpb587.me/blob/main/content/post/2020/nested-taxonomies-in-hugo-20200309.md) site.
