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 templatessingle.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