Guide

Dynamic taxonomies

Taxonomies classify posts into browsable archives. category, tag and series are built in and keep their historical URLs, templates and feeds; any number of additional taxonomies can be declared in configuration. A working project lives in examples/dynamic-taxonomies/.

Configuration

 1taxonomies:
 2  technology:
 3    label: Technologies    # plural heading (default: title-cased name)
 4    singular: Technology   # singular heading (default: title-cased name)
 5    path: technology       # URL segment (default: the taxonomy name)
 6    field: technology      # frontmatter field read (default: the taxonomy name)
 7    multiple: true         # false = exactly one value per post
 8    archive: true          # emit /technology/ + /technology/<term>/
 9    feed: false            # true = Atom feed per term (/technology/go/feed.xml)
10    sitemap: true          # include archives in sitemap.xml
11    template: ""           # explicit index template (see fallback chain below)
12    term_template: ""      # explicit term template
13    sort: name             # term ordering: name | count | weight
14    paginate: 0            # posts per term-archive page; 0 = use the global paginate
15    case_sensitive: false  # "Go" and "go" merge into one term by default
16    slugify: true          # URL slugs derived from term names
17    generate_empty: false  # true = archives for zero-post terms from data files

paginate sets the page size for this taxonomy's term archives, overriding the site-wide paginate; leave it 0 to inherit the global value. A site with many tags but few categories can page each differently — paginate: 50 on tag, paginate: 12 on category.

Taxonomy names must match [a-z][a-z0-9_-]*. Every taxonomy needs a unique path, and the segments author, page and configured language codes are reserved. The built-in category, tag, series and author archives are all driven by the single taxonomy registry, but render with byte-identical legacy-compatible output. Overriding category, tag or series adjusts their metadata (labels, feed on/off for helpers); custom path/template overrides for the built-ins are ignored, since their output is held stable (see Deferred).

Assigning terms in frontmatter

Three sources are merged per taxonomy, in priority order:

1---
2title: Cross-compiling Go and Rust
3taxonomies:            # 1. the generic map (highest priority)
4  technology: [Go, Rust]
5  platform: [Linux]
6technology: [Go]       # 2. the configured direct field
7tags: [tutorial]       # 3. legacy fields (tags/category/categories/series)
8---

Multi-value taxonomies merge and deduplicate across sources. A single-value taxonomy (multiple: false) with two distinct values after deduplication fails the build. Values assigned to tag/series through the generic map are synced back onto the legacy fields, so the classic /tag/…/ archives include them.

Term identity is normalized: surrounding/inner whitespace collapses and, unless case_sensitive: true, comparison is Unicode-lowercased — Go, go and GO are one term whose display name is the first spelling seen. Two distinct terms slugifying to the same URL (e.g. C++ and C--c) fail the build; set an explicit slug in the term metadata to resolve it. Term and index URLs are also validated against page, post and alias URLs — collisions fail the build instead of overwriting output.

Term metadata

data/taxonomies/<taxonomy>.yaml enriches terms (keys are normalized names):

1go:
2  name: Go              # display-name override
3  slug: golang          # slug override
4  description: The Go programming language
5  weight: 10            # used by sort: weight (descending)
6  data:                 # free-form, exposed as .Data on the term
7    color: "#00ADD8"

With generate_empty: true, metadata-only terms get archive pages even before any post uses them.

Templates

Archive pages pick the first template that exists in the theme:

Page Fallback chain
Taxonomy index (/technology/) template: override → taxonomy-<name>.htmltaxonomy.htmlarchive.htmlcategory.html
Term archive (/technology/go/) term_template: override → taxonomy-<name>-term.htmltaxonomy-term.htmlarchive.htmlcategory.html

The index context provides .Taxonomy (Name/Label/Singular/Path/URL) and .Terms (each Name/Slug/URL/Description/Count/Weight/Data). The term context provides .Taxonomy, .Term, .Posts (newest first), .Pager and — for compatibility with category.html.Category, .Kind and .Name. With paginate set, term archives paginate to /technology/go/page/2/, and .CanonicalURL names the page currently being rendered.

Template helpers

Helper Example Result
taxonomies {{range taxonomies}}{{.Label}} every definition, stable order
taxonomy {{with taxonomy "technology"}}{{.URL}} one definition view
taxonomyTerms {{range taxonomyTerms "technology"}}… sorted terms (current language)
pageTerms {{range pageTerms "technology" .Page}}… a page's terms as full views
termURL {{termURL "technology" "Go"}} /technology/go/
hasTerm {{if hasTerm "technology" "Go" .Page}} normalized membership test
pagesByTerm {{range pagesByTerm "technology" "Go"}}… the term's posts, newest first

Every helper above takes a taxonomy name. The examples use a custom one, so it is worth stating the built-in names outright — they are category, tag and series, singular, not the plural frontmatter field names:

{{ termURL "category" "Marsaskala" }}   {{/* ✅ /category/marsaskala/ */}}
{{ termURL "categories" "Marsaskala" }} {{/* ❌ "" — no such taxonomy */}}

An unknown taxonomy name returns an empty string rather than an error, so the mistake shows up as href="" rather than as a failed build.

Author is not one of them. The author archive is keyed on an author id rather than a frontmatter field, so it is driven outside the registry and termURL "author" returns "". Use authorURL instead — it takes the author id a post carries, the post itself, an author record or a display name, and returns /author/<slug>/ (see docs/TEMPLATE_HELPERS).

Multilingual builds

With i18n.enabled, terms live in per-language buckets and custom archives are emitted per language with the usual prefix rules: /technology/… for the default language and /en/technology/… for others. Feeds, sitemap entries and the helpers all follow the language of the page being rendered.

  • feed: true (plus global feed: true) writes an Atom feed per term.
  • sitemap: true (default) adds the taxonomy index and each term archive. sitemap: false keeps them out without removing them: archive is the separate switch that decides whether they are written at all. It applies to the built-in category and tag as well as to custom taxonomies — before 1.8.58 it parsed and validated on the built-ins and did nothing, so a site whose theme marked its tag archives noindex kept advertising them anyway. author is driven outside the registry and is not configurable this way.
  • The search index and JSON output records carry a taxonomies map ({"technology": ["Go", "Rust"], …}) for client-side filtering.

Deferred features

Not part of this release; tracked for later:

  • Hierarchical taxonomies (nested terms with rollup counts).
  • Term aliases/redirects and per-language translated term names.
  • Custom path/template overrides for the built-in category/tag/series/author archives (they are folded onto the registry but keep byte-for-byte legacy output, so per-built-in path/template overrides don't apply).