# How Hugo Generates Taxonomies (Tags, Categories) and Their Term Pages

> Discover how Hugo generates taxonomies like tags and categories. Learn about the four-step pipeline for creating taxonomy and term pages, directly from the gohugoio/hugo repository.

- Repository: [GoHugo.io/hugo](https://github.com/gohugoio/hugo)
- Tags: internals
- Published: 2026-02-28

---

**Hugo generates taxonomies by creating two virtual page types—taxonomy list pages (`KindTaxonomy`) for overview pages like `/tags/` and term pages (`KindTerm`) for individual terms like `/tags/go/`—through a four-step pipeline that parses configuration, walks the content tree, assembles virtual pages, and exposes data to templates.**

In the `gohugoio/hugo` static site generator, taxonomy generation is handled entirely in memory during the site build process. Unlike static files, taxonomy pages are virtual resources created by the page assembler based on content relationships defined in front matter. Understanding this pipeline is essential for customizing taxonomy templates and optimizing site architecture.

## Understanding Hugo's Taxonomy Architecture

Hugo distinguishes between two distinct virtual page kinds when generating taxonomy structures, each serving a different purpose in the content hierarchy.

### Taxonomy List Pages (KindTaxonomy)

The taxonomy list page represents the top-level overview for an entire taxonomy classification. In [`resources/kinds/kinds.go`](https://github.com/gohugoio/hugo/blob/main/resources/kinds/kinds.go), this is defined as `KindTaxonomy`. For example, the page at `/tags/` or `/categories/` is a `KindTaxonomy` page that lists all terms within that taxonomy. These pages are generated automatically when the site configuration includes the taxonomy, regardless of whether individual term content files exist.

### Term Pages (KindTerm)

Term pages represent individual taxonomy values, such as `/tags/go/` or `/categories/tutorial/`. Defined as `KindTerm` in [`kinds.go`](https://github.com/gohugoio/hugo/blob/main/kinds.go), these pages aggregate all content pages that reference that specific term. Hugo creates these pages in two ways: automatically for terms referenced in content front matter, or from explicit content files (e.g., [`content/tags/go.md`](https://github.com/gohugoio/hugo/blob/main/content/tags/go.md)) that allow authors to add custom metadata to term pages.

## The Four-Step Taxonomy Generation Pipeline

The taxonomy generation process in `gohugoio/hugo` follows a strict pipeline defined across several files in the `hugolib` package. Each step transforms configuration and content into renderable virtual pages.

### Step 1: Parsing Taxonomy Configuration

Hugo begins by reading the site configuration to identify which taxonomies to generate. In [`hugolib/site.go`](https://github.com/gohugoio/hugo/blob/main/hugolib/site.go), the configuration is parsed into an internal `taxonomiesConfig` type:

```go
// hugolib/site.go – internal type for the config map
type taxonomiesConfig map[string]string

```

The `Taxonomies()` method (lazy-loaded) converts this configuration into a list of `viewName` structs defined in [`hugolib/content_map_page.go`](https://github.com/gohugoio/hugo/blob/main/hugolib/content_map_page.go) (lines 33-36). Each `viewName` stores the singular and plural forms of the taxonomy along with a tree key used for URL construction.

### Step 2: Walking the Content Tree with CreateSiteTaxonomies

The core aggregation logic resides in `CreateSiteTaxonomies` within [`hugolib/content_map_page.go`](https://github.com/gohugoio/hugo/blob/main/hugolib/content_map_page.go). This function walks the content tree to collect all term references and build the `TaxonomyList` data structure:

```go
// hugolib/content_map_page.go – CreateSiteTaxonomies
func (m *pageMap) CreateSiteTaxonomies(ctx context.Context) (page.TaxonomyList, error) {
    taxonomies := make(page.TaxonomyList)
    …
    for _, viewName := range m.cfg.taxonomyConfig.views {
        // Walk the page tree under the taxonomy’s tree key
        w := &doctree.NodeShiftTreeWalker[contentNode]{ … }
        w.Handle = func(s string, n contentNode) (radix.WalkFlag, error) {
            p := n.(*pageState)
            switch p.Kind() {
            case kinds.KindTerm:
                if !p.m.shouldList(true) { return radix.WalkContinue, nil }
                taxonomy := taxonomies[viewName.plural]
                termKey := strings.ToLower(p.m.term) // e.g. “go”
                // Gather all pages that belong to this term
                m.treeTaxonomyEntries.WalkPrefix(doctree.LockTypeRead,
                    paths.AddTrailingSlash(s),
                    func(ss string, wn *weightedContentNode) (bool, error) {
                        taxonomy[termKey] = append(taxonomy[termKey],
                            page.NewWeightedPage(wn.weight, wn.n.(page.Page), wn.term.Page()))
                        return false, nil
                    })
            }
            return radix.WalkContinue, nil
        }
        if err := w.Walk(ctx); err != nil { … }
    }
    // Sort each term’s pages
    for _, taxonomy := range taxonomies {
        for _, v := range taxonomy { v.Sort() }
    }
    return taxonomies, nil
}

```

This function specifically handles `KindTerm` pages, using `treeTaxonomyEntries.WalkPrefix` to gather all content pages associated with each term. It populates the `TaxonomyList` map where keys are taxonomy names (plural) and values are maps of term keys to `WeightedPages` collections.

### Step 3: Creating Virtual Pages in the Assembler

While `CreateSiteTaxonomies` builds the data structures, the actual virtual pages are instantiated in [`hugolib/content_map_page_assembler.go`](https://github.com/gohugoio/hugo/blob/main/hugolib/content_map_page_assembler.go). The assembler creates two types of pages:

For term pages (`KindTerm`):

```go
// hugolib/content_map_page_assembler.go – term page creation (excerpt)
case kinds.KindTerm:
    p := &pageState{
        // …
        Kind:    kinds.KindTerm,
        // term, singular/plural etc. are stored in the page’s meta
    }

```

For taxonomy list pages (`KindTaxonomy`), the process is similar but sets `Kind = kinds.KindTaxonomy`. These pages are inserted into `treePages` and assigned URLs based on the taxonomy's tree key and the lower-cased term name. They exist only in memory during the build, derived from the content relationships rather than physical files.

### Step 4: Exposing Data to Templates

Finally, Hugo exposes the taxonomy data through the `Site` object and template functions. In [`hugolib/site.go`](https://github.com/gohugoio/hugo/blob/main/hugolib/site.go) (around line 90), the `Taxonomies()` method returns the `page.TaxonomyList` built during the creation phase.

The [`resources/page/taxonomy.go`](https://github.com/gohugoio/hugo/blob/main/resources/page/taxonomy.go) file defines helper methods that templates use to access taxonomy data:

```go
// resources/page/taxonomy.go – helpers
func (i Taxonomy) Page() Page                 // returns the taxonomy list page
func (i Taxonomy) Get(key string) WeightedPages // pages for a term
func (i Taxonomy) Alphabetical() OrderedTaxonomy // sorted terms

```

Template usage examples:

```go
{{/* List all tags */}}
{{ range .Site.Taxonomies.tags.Alphabetical }}
    <a href="{{ .Pages | first.Permalink }}">{{ .Term }}</a> ({{ .Count }})
{{ end }}

{{/* Render a term page */}}
{{ range .Pages }}
    <h2><a href="{{ .Permalink }}">{{ .Title }}</a></h2>
{{ end }}

```

## Configuring Taxonomies in Hugo

Before Hugo can generate taxonomy pages, you must define them in your site configuration. The default configuration includes `tags` and `categories`, but you can customize or add taxonomies in [`config.toml`](https://github.com/gohugoio/hugo/blob/main/config.toml):

```toml
[taxonomies]
  tag = "tags"
  category = "categories"
  series = "series"

```

Each entry maps the singular form (key) to the plural form (value). The plural form determines the URL structure (e.g., `/tags/`) and the access key in templates (e.g., `.Site.Taxonomies.tags`).

## Key Source Files in the Hugo Repository

Understanding the taxonomy generation pipeline requires familiarity with these specific files in `gohugoio/hugo`:

| File | Purpose |
|------|---------|
| [`hugolib/site.go`](https://github.com/gohugoio/hugo/blob/main/hugolib/site.go) | Parses taxonomy configuration via `taxonomiesConfig` and exposes `Site.Taxonomies()` |
| [`hugolib/content_map_page.go`](https://github.com/gohugoio/hugo/blob/main/hugolib/content_map_page.go) | Contains `CreateSiteTaxonomies()` which walks the content tree and builds `TaxonomyList` |
| [`hugolib/content_map_page_assembler.go`](https://github.com/gohugoio/hugo/blob/main/hugolib/content_map_page_assembler.go) | Creates virtual `KindTaxonomy` and `KindTerm` pages and inserts them into `treePages` |
| [`resources/page/taxonomy.go`](https://github.com/gohugoio/hugo/blob/main/resources/page/taxonomy.go) | Defines `Taxonomy`, `WeightedPages`, and helper methods like `Alphabetical()` |
| [`resources/kinds/kinds.go`](https://github.com/gohugoio/hugo/blob/main/resources/kinds/kinds.go) | Defines `KindTaxonomy` and `KindTerm` constants |
| [`create/skeletons/theme/layouts/taxonomy.html`](https://github.com/gohugoio/hugo/blob/main/create/skeletons/theme/layouts/taxonomy.html) | Default layout template for taxonomy lists |

## Summary

- **Hugo generates taxonomies** by creating virtual pages for both taxonomy lists (`KindTaxonomy`) and individual terms (`KindTerm`), rather than relying on physical files.

- **Configuration parsing** happens in [`hugolib/site.go`](https://github.com/gohugoio/hugo/blob/main/hugolib/site.go), where the `taxonomies` map is converted into internal `viewName` structs defining singular/plural pairs.

- **Data aggregation** occurs in `CreateSiteTaxonomies` within [`hugolib/content_map_page.go`](https://github.com/gohugoio/hugo/blob/main/hugolib/content_map_page.go), which walks the content tree, identifies `KindTerm` pages, and populates `TaxonomyList` with `WeightedPages` for each term.

- **Virtual page creation** is handled by [`hugolib/content_map_page_assembler.go`](https://github.com/gohugoio/hugo/blob/main/hugolib/content_map_page_assembler.go), which instantiates the taxonomy pages and assigns them URLs based on tree keys and lower-cased term names.

- **Template exposure** happens through `.Site.Taxonomies`, defined in [`resources/page/taxonomy.go`](https://github.com/gohugoio/hugo/blob/main/resources/page/taxonomy.go), providing methods like `Alphabetical()` and `Get()` for rendering taxonomy data.

## Frequently Asked Questions

### How does Hugo determine the URL structure for taxonomy pages?

Hugo builds taxonomy URLs using the **tree key** defined in the taxonomy configuration (typically the plural form) combined with the lower-cased term name. For example, a "go" tag creates the URL `/tags/go/` where `tags` is the plural configuration key. This logic is implemented in [`hugolib/content_map_page_assembler.go`](https://github.com/gohugoio/hugo/blob/main/hugolib/content_map_page_assembler.go) when virtual pages are inserted into `treePages` with their respective paths.

### What is the difference between KindTaxonomy and KindTerm in Hugo?

**`KindTaxonomy`** represents the list page for an entire taxonomy classification (e.g., `/tags/` or `/categories/`), showing all available terms within that taxonomy. **`KindTerm`** represents the page for a specific taxonomy value (e.g., `/tags/go/`), displaying all content pages associated with that specific term. These constants are defined in [`resources/kinds/kinds.go`](https://github.com/gohugoio/hugo/blob/main/resources/kinds/kinds.go) and determine which layout templates Hugo selects and how the pages are rendered.

### How can I customize the layout for taxonomy pages?

Hugo looks for specific layout templates based on the page kind and lookup order. For taxonomy list pages (`KindTaxonomy`), create [`layouts/_default/taxonomy.html`](https://github.com/gohugoio/hugo/blob/main/layouts/_default/taxonomy.html) or [`layouts/_default/list.html`](https://github.com/gohugoio/hugo/blob/main/layouts/_default/list.html). For individual term pages (`KindTerm`), use [`layouts/_default/term.html`](https://github.com/gohugoio/hugo/blob/main/layouts/_default/term.html) or [`layouts/_default/list.html`](https://github.com/gohugoio/hugo/blob/main/layouts/_default/list.html). You can also create taxonomy-specific layouts like [`layouts/tags/list.html`](https://github.com/gohugoio/hugo/blob/main/layouts/tags/list.html) or [`layouts/tags/tag.html`](https://github.com/gohugoio/hugo/blob/main/layouts/tags/tag.html) (depending on your taxonomy name) to target specific taxonomies with custom designs.

### Where does Hugo store the mapping between content and taxonomy terms?

Hugo stores the taxonomy-to-content mapping in the **`TaxonomyList`** data structure, which is built during the site assembly process in [`hugolib/content_map_page.go`](https://github.com/gohugoio/hugo/blob/main/hugolib/content_map_page.go). Specifically, the `CreateSiteTaxonomies` function populates this structure by walking `treeTaxonomyEntries`—a radix tree containing all page-term associations. The resulting map organizes content by taxonomy plural name, then by lower-cased term key, storing `WeightedPages` collections that link back to the actual content pages. This structure is then exposed to templates via `.Site.Taxonomies`.