fizz.today

Hugo title-cases your tags everywhere (and how to stop it)

Tags in my frontmatter are lowercase:

tags = ["aws", "eks", "headscale"]

Tags in the rendered HTML came out title-cased — in three different places:

<!-- OpenGraph meta -->
<meta property="article:tag" content="Aws" />

<!-- Schema.org microdata -->
<meta itemprop="keywords" content="Aws,Eks,Headscale">

<!-- Tag links on the post page and blog index -->
<a href="/til/aws/">#Aws</a>

What doesn’t work

# hugo.toml
pluralizeListTitles = false
capitalizeListTitles = false

These settings only affect list page titles (like /tags/aws/ rendering as “aws” instead of “Aws” in the heading). They have zero effect on OpenGraph meta, microdata, or tag links.

The cause

Hugo title-cases tags in three separate places, each from a different template:

  1. _internal/opengraph.html — calls .Title on each tag for article:tag meta
  2. _internal/schema.html — calls .Title on each tag for itemprop="keywords" meta
  3. Theme templates — single.html and list.html use .LinkTitle or .Page.Title when rendering tag links

No config setting overrides any of these. They’re hardcoded in Hugo’s source and in the theme templates.

The fix: three template overrides

1. OpenGraph + schema.org: layouts/partials/seo_tags.html

Most themes include Hugo’s internal templates via a partial. Override it at the project level to use raw tag values:

<!-- OpenGraph tags — use raw {{ . }} instead of .Title -->
{{ range .Params.tags }}<meta property="article:tag" content="{{ . }}">
{{ end }}

<!-- Schema.org keywords — use raw $tag instead of .Title -->
{{ if .Params.tags }}<meta itemprop="keywords" content="{{ range $i, $tag := .Params.tags }}{{ if $i }},{{ end }}{{ $tag }}{{ end }}">{{ end }}

Replace both {{ template "_internal/opengraph.html" . }} and {{ template "_internal/schema.html" . }} with inline versions that use the raw tag strings.

2. Post tag links: layouts/_default/single.html

The theme iterates with .GetTerms "tags" and renders .LinkTitle, which Hugo title-cases. Override with:

{{ range .Params.tags }}
<a href="{{ printf "/til/%s/" (. | urlize) }}">#{{ . }}</a>
{{ end }}

The theme iterates with .Site.Taxonomies.tags and renders .Page.Title, which Hugo also title-cases. Override with:

{{ range $tag, $_ := .Site.Taxonomies.tags }}
<a href="{{ printf "/til/%s/" ($tag | urlize) }}">#{{ $tag }}</a>&nbsp;
{{ end }}

Result

<meta property="article:tag" content="aws">
<meta itemprop="keywords" content="aws,eks,headscale">
<a href="/til/aws/">#aws</a>

Lowercase everywhere. Three overrides for one config setting that should exist but doesn’t.

#hugo #seo