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>.html → taxonomy.html → archive.html → category.html |
Term archive (/technology/go/) |
term_template: override → taxonomy-<name>-term.html → taxonomy-term.html → archive.html → category.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.
Feeds, sitemap and search
feed: true(plus globalfeed: true) writes an Atom feed per term.sitemap: true(default) adds the taxonomy index and each term archive.sitemap: falsekeeps them out without removing them:archiveis the separate switch that decides whether they are written at all. It applies to the built-incategoryandtagas 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 archivesnoindexkept advertising them anyway.authoris driven outside the registry and is not configurable this way.- The search index and JSON output records carry a
taxonomiesmap ({"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/templateoverrides 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-inpath/templateoverrides don't apply).