# Nested taxonomy terms

**URL:** <https://discourse.gohugo.io/t/nested-taxonomy-terms/39249>\
**Category:** tips & tricks\
**Tags:** taxonomy\
**Created:** [June 25, 2022, 9:55pm UTC](https://discourse.gohugo.io/t/nested-taxonomy-terms/39249 "2022-06-25T21:55:42Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![thm\_pedro](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/thm_pedro/32/17159_2.png) [@thm\_pedro](https://discourse.gohugo.io/u/thm_pedro)\
**Post date:** [June 25, 2022, 9:55pm UTC](https://discourse.gohugo.io/t/nested-taxonomy-terms/39249/1 "2022-06-25T21:55:42Z")

</div>

I’m working on a wikipedia-based site and I needed to replicate its nested categories system, like in here [Category:Philosophy - Wikipedia](https://en.m.wikipedia.org/wiki/Category:Philosophy).

It isn’t something Hugo or any other SSG provides out of the box. I did some research and found some posts here asking about related things, but none provided a solution.

I thought it was not possible to do something like that in the current build.

Fast forward a few days, I had an epiphany, read the taxonomies documentation and set out to code. After some tests it seems to be functional and I decided to share with the community. Here’s how it’s built.

Ps: it nests taxonomy _terms_, and not the taxonomies themselves. This was something I took a while to realize I needed to do.

Create a data file with an entry for the taxonomies (as many as you need), and under it map the tree of relations between terms, like this:  
`data/terms_trees.yaml`

```auto
categories:
  termA:
    termA1:
  termB:
    termB2:
      termB11:
        termB111:
    termB1:
      termB21:
      termB22:
      aeste:

```

Important!  
The taxonomy entry has to be its exact plural, and each term **must** have a “:” after it’s name to indicate it’s a map, otherwise the code will break.

And if you put the data file inside a folder in your data folder you will have to modify the code accordingly to point it to the right place, which shouldn’t be hard.

Then create a partial to work like sort of a recursive function.  
`partials/functions/list-subterms.html`

```auto
{{ $target := .target }}
{{ $page := .page }}
{{ $singular := index .page.Data "Singular" }}
{{ $plural := index .page.Data "Plural" }}
{{ $existingterms := .existingterms }}
{{ $scratch := newScratch }}

{{ range $key, $item := .start }}
{{ if eq $key $target }}
{{ $scratch.Add "targeted branch" $item }}
{{ break }}
{{ end }}
{{- partial "functions/list-subterms.html" (dict "start" $item "target" $target "page" $page "existingterms"
$existingterms) -}}
{{ end }}

{{ $targetedbranch := $scratch.Get "targeted branch" }}
{{ with $targetedbranch }}
{{ range $key, $item := . }}
{{ range $existingterms }}
{{ if eq (lower $key) . }}
{{ $scratch.Add "final list" (slice (site.GetPage (printf "/%s/%s" $plural ($key | urlize)))) }}
{{ end }}
{{ end }}
{{ end }}
{{ end }}

{{- $final := ($scratch.Get "final list") -}}

<!-- create a list with all uppercase letters -->
{{ $letters := split "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "" }}

<!-- range all pages sorted by their title -->
{{ range $index, $item := $final.ByTitle }}
    <!-- get the first character of each title. Assumes that the title is never empty! -->
    {{ $firstChar := substr $item.Title 0 1 | upper }}
    <!-- in case $firstChar is a letter -->
    {{ if $firstChar | in $letters }}
        <!-- get the current letter -->
        {{ $curLetter := $scratch.Get "curLetter" }}
        <!-- if $curLetter isn't set or the letter has changed -->
        {{ if ne $firstChar $curLetter }}
            <!-- update the current letter and print it -->
            {{ $scratch.Set "curLetter" $firstChar }}

            {{ if ne $index 0 }}</div>{{ end }}
            <div class="subcategory-group">
            <h3>{{ $firstChar }}</h3>
        {{ end }}
        <ul><li><a href="{{ $item.Permalink }}">{{ $item.Title }}</a></li></ul>
    {{ end }}
{{ end }}
</div>

```

It’s currently set up to separate the terms by initial letter, with I achieved with a very handy code I found in this same forum but I don’t remember the original author (sorry).

Then in your \_default/list.html or in whatever list template you want place this little ugly monster of a code:

```auto
{{ if eq .Page.Kind "term" }}
{{ $thistax := index $.Page.Data "Plural"}}
{{ $thisterm := index $.Page.Data "Term"}}
{{ range $index, $item := .Site.Taxonomies }}
{{ if eq $index $thistax }}
{{ range $i, $j := $item }}
{{ $.Scratch.Add "existingterms" (slice $i) }}
{{ end }}
{{ end }}
{{ end }}
{{ range $datatax, $map := .Site.Data.terms_trees }}
{{ if eq $datatax $thistax }}
<h2>Sub{{ index $.Page.Data "Plural" }}</h2>
<p>This {{ index $.Page.Data "Singular" }} has the following {{ len . }} sub{{ index $.Page.Data "Plural" }}.</p>
{{- partial "functions/list-subterms.html" (dict "start" $map "target" $thisterm "page" $.Page "existingterms"
($.Scratch.Get "existingterms") ) -}}

{{ end }}
{{ end }}
{{ end }}

```

And that’s it! Please pardon my spaghetti code, I’m just an amateur…

Unfortunately my site isn’t ready to be put online, but if the forum allows me to edit the post later I will provide a working example here.

---

<div class="post-metadata">

**Author:** ![gaetawoo](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/gaetawoo/32/16789_2.png) [@gaetawoo](https://discourse.gohugo.io/u/gaetawoo)\
**Post date:** [June 27, 2022, 7:20pm UTC](https://discourse.gohugo.io/t/nested-taxonomy-terms/39249/2 "2022-06-27T19:20:21Z")

</div>

Pretty cool, i hope it works out well!

Curious as to why you used Scratch variables instead of regular ones

---

<div class="post-metadata">

**Author:** ![thm\_pedro](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/thm_pedro/32/17159_2.png) [@thm\_pedro](https://discourse.gohugo.io/u/thm_pedro)\
**Post date:** [June 28, 2022, 6:32pm UTC](https://discourse.gohugo.io/t/nested-taxonomy-terms/39249/3 "2022-06-28T18:32:50Z")

</div>

No particular reason I can remember right now, I think it just came out this way

I guess I could have used regular ones too

---

<div class="post-metadata">

**Author:** ![jollyfrog](https://avatars.discourse-cdn.com/v4/letter/j/ecccb3/32.png) [@jollyfrog](https://discourse.gohugo.io/u/jollyfrog)\
**Post date:** [August 6, 2022, 12:09pm UTC](https://discourse.gohugo.io/t/nested-taxonomy-terms/39249/4 "2022-08-06T12:09:30Z")

</div>

@thm_pedro This is fascinating, although I understand very little of it currently.

I am looking to create a simple business directory on one of my hugo sites and I would like the user to be able to select business services by city.

For example, selecting businesses offering “tree surgery” services in Birmingham while also being able to select tree surgery in Nottingham.

- Birmingham

- Nottingham

- Manchester

Would you say your method is suitable for this kind of structure or is there another way to acheive this?

---

<div class="post-metadata">

**Author:** ![thm\_pedro](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/thm_pedro/32/17159_2.png) [@thm\_pedro](https://discourse.gohugo.io/u/thm_pedro)\
**Post date:** [August 6, 2022, 1:07pm UTC](https://discourse.gohugo.io/t/nested-taxonomy-terms/39249/5 "2022-08-06T13:07:28Z")

</div>

If i understand what you mean what you want is different, maybe you will have to grab values that are in the [intersection](https://gohugo.io/functions/intersect#readout) of terms, say, `Birmingham` and `plumbing`.

I would start building a list of cities and a list of services, then intersect them and display

---

<div class="post-metadata">

**Author:** ![jollyfrog](https://avatars.discourse-cdn.com/v4/letter/j/ecccb3/32.png) [@jollyfrog](https://discourse.gohugo.io/u/jollyfrog)\
**Post date:** [August 6, 2022, 2:29pm UTC](https://discourse.gohugo.io/t/nested-taxonomy-terms/39249/6 "2022-08-06T14:29:09Z")

</div>

That’s useful, thank you. I haven’t come across that function, but I will look into how it works and what it does.

The official documentation on intersection seems a bit light, are you able to recommend any other resources?

Apologies for my ignorance, I am a novice.

---

<div class="post-metadata">

**Author:** ![thm\_pedro](https://yyz2.discourse-cdn.com/flex036/user_avatar/discourse.gohugo.io/thm_pedro/32/17159_2.png) [@thm\_pedro](https://discourse.gohugo.io/u/thm_pedro)\
**Post date:** [August 13, 2022, 11:02am UTC](https://discourse.gohugo.io/t/nested-taxonomy-terms/39249/7 "2022-08-13T11:02:28Z")

</div>

I don’t know resources other than this forum, sorry

Hugo docs requise some of trial and error, but that’s the fun of it
