Hugo Templates and Shortcodes

Last update: 2025-10-30 09:45:02 +0800 +0800
tags: Hugo | Go
Available translations: 日本語 / 中文(简体) / 中文(繁體)

Hugo templates combine Go’s template language with Hugo functions and page objects. Shortcodes use the same language, but content authors invoke them from Markdown while Hugo constructs .Content.

Follow Go Template Context and Scope #

Template actions appear inside {{ and }}. A pipeline passes the result of each command to the final argument of the next:

go-html-template
{{ .Title | strings.TrimSpace | htmlEscape }}

Dot (.) is the current context. with, range, and template calls can rebind it, so capture an outer object before entering a narrower scope:

go-html-template
{{ $page := . }}
{{ range .Pages }}
  <a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a>
  <span>{{ $page.Site.Title }}</span>
{{ end }}

Variables begin with $ and remain visible through the scope in which they are declared. The special root variable $ initially refers to the context passed to the template, but explicitly named variables make nested code easier to review.

Go template functions return values that pipelines can transform. Some also return an error as their final result; a non-nil error stops template execution. Use with for optional values and errorf when absence violates a content contract rather than silently emitting incomplete markup.

Preserve Contextual Escaping #

HTML layouts execute through Go’s html/template, which escapes values according to whether they appear in text, an attribute, a URL, CSS, or JavaScript:

go-html-template
<a href="{{ .Params.target }}">{{ .Params.label }}</a>

Functions such as safeHTML, safeCSS, safeJS, and safeURL mark a value as trusted for one context. They do not sanitize untrusted input. Apply them only to content generated or validated by the site, because using the wrong safe type can create a cross-site scripting path.

Hugo’s template.HTML-like safe values also cross a partial boundary. A partial should therefore document whether it expects plain text, a URL, a page object, or trusted markup.

Pass Explicit Data to Partials #

Partials under layouts/_partials/ render reusable template fragments. The caller chooses their dot:

go-html-template
{{ partial "card.html" (dict
  "page" .
  "compact" true
) }}

Inside card.html, .page and .compact are available, but the original page is not implicitly restored. Explicit dictionaries prevent a partial from depending on whichever dot happened to be active at its call site.

partialCached stores a partial’s result for the build:

go-html-template
{{ partialCached "menu.html" . .Language.Lang .Section }}

Cache variants must include every input that can change the output. Without variant keys, page-specific content can be reused on unrelated pages.

Locate Shortcode Templates and Resources #

With current Hugo versions, project shortcode templates reside under layouts/_shortcodes/; modules or themes can provide a lower-precedence definition. Subdirectories form part of the shortcode name. A call to docs/note resolves a template such as:

text
layouts/_shortcodes/docs/note.html

The invocation belongs in a content page, whether that page is a regular file or a bundle’s index.md. Assets do not belong in the layout directory. A shortcode used by a leaf bundle can resolve sibling resources through .Page.Resources.Get, while site-wide assets come from the global resource pipeline.

A shortcode receives a shortcode context rather than the surrounding page as dot. Common members include .Page, .Params, .Get, .Inner, .InnerDeindent, .Ordinal, and .Parent. Named and positional arguments should not be mixed ambiguously:

go-html-template
{{ $src := .Get "src" }}
{{ with .Page.Resources.Get $src }}
  <img src="{{ .RelPermalink }}" alt="{{ $.Get "alt" }}">
{{ else }}
  {{ errorf "shortcode image %q not found in %s" $src $.Page.Path }}
{{ end }}

Choose the Markdown Boundary #

Hugo provides two delimiter forms:

  • {{< note >}} inserts shortcode template output without sending that output through the Markdown renderer.
  • {{% note %}} sends shortcode template output through Markdown rendering.

The percentage form is appropriate when the shortcode produces Markdown. The angle-bracket form is appropriate when the template owns the final HTML. Indentation still matters in the Markdown form because four spaces can turn generated content into a code block. Goldmark’s unsafe-HTML setting also affects raw HTML that enters the Markdown path.

For a paired shortcode, .Inner contains its inner content after Hugo resolves nested shortcode calls. The shortcode template decides whether to render that value directly or pass it through .Page.RenderString; do not render an already-rendered value a second time.

Emit Literal Delimiters Safely #

Markdown code fences do not protect shortcode-looking text because shortcode expansion occurs before the Markdown renderer handles the fence. Hugo’s comment escape emits a literal example without attempting lookup:

text
{{< note >}}
{{% note %}}

HTML entities are another option when the rendered page only needs to display the delimiters. Use the comment escape when readers should be able to copy the literal shortcode syntax from the rendered result.

Diagnose Lookup and Execution Failures #

An unknown shortcode normally reports the content file and shortcode name. Check, in order:

  1. The name and case used at the call site.
  2. The _shortcodes directory and any subdirectory in the name.
  3. The active module mounts and theme precedence.
  4. Whether the project version expects the current layout namespace.
  5. Whether template execution failed after lookup because an argument or resource was absent.

Use warnf for actionable diagnostics that may continue and errorf for broken invariants that must fail the build. Templates provide site structure, partials reuse structure, and shortcodes bridge author-controlled content to a deliberately bounded template API.

Comments

Read or comment on GitHub (sign-in required to comment)