Hugo Templates and Other Features

The Hugo static site generator takes some plain-text content, marries it to a bunch of HTML templates, and produces a set of complete, static HTML pages that can be served by any generic, stand-alone web server. It selects templates based on the type of the content, and its position in the filesystem.

Part 4: Templates, Template Selection and Composition, Shortcodes

Hugo uses Go templates to turn Markdown into HTML. Hugo’s rules for matching a piece of content with a template are complex. Moverover, each template may in turn be composed of smaller component templates. Shortcodes provide a way to inject template logic directly into Markdown content.

Template Selection

In general, it is unnecessary to configure which template will be used to render a given piece of content: just drop a Markdown file into the content/ directory, and Hugo will select the most appropriate template, automatically.

Hugo tries to find the most specific layout available for each piece of content. To do so, it takes into account both the type of content, as well as its place in the directory hierarchy. It then tries to find a template, suitable for the type at a comparable location in the layouts/ directory. The detailed rules for template selection are complex, but in practice, only a handful of observations suffice:

  1. First, Hugo determines the type (or “kind”, as in “kind of page”) of the content. The primary distinction is whether the input represents the content for a single page, or does it represent a list of items (such as the files in a directory or a list of tags). Based on this distinction, either a single page or a list page template will be used; the filenames of the templates are expected to be single.html or list.html.

    Other possible “kinds” are home, section, taxonomy, taxonomyTerm, all of which, including home, are considered list templates; and RSS, sitemap, robotsTXT, and 404. Breaking all the rules, the template for the home page is called index.html and is located in the layouts/ directory itself, not in a subdirectory.

  2. Next, Hugo considers the location of the content in the source directory, and tries to find a template at a matching location in the layouts/ directory. The idea is that the template directory may replicate some of the directory hierarchy of the source tree, and use this information when selecting a template.

    This is actually quite intuitive. To render the file found at content/blog/some-post.md, Hugo will choose the template found at layouts/blog/single.html (if it exists), rather than the one at layouts/_default/single.html.

  3. To locate a template, Hugo will look in two locations: first in the projects layouts/ directory, and only then in the theme’s layouts/ directory. Only if it doesn’t find a template in either location will it move up one level in the filesystem hierarchy.

    This “cascade” provides a non-intrusive way to customize a theme without actually having to modify the theme files themselves. (In principle, this also allows to upgrade the theme to a new version, without destroying the custom overrides.)

  4. Finally, a specific template file can be identified in the frontmatter of a piece of content using the layout parameter.

There are additional rules, but this will suffice in practice. To summarize:

  • Most content constitutes either a single or a list (or maybe a home) page.

  • Templates are selected by the closest corresponding location of the input content. Exploit this behavior to override themes with local customizations.

  • A specific template can be configured in the content’s frontmatter.

Because template selection is partially based on location (that is, filesystem paths or, equivalently, URLs) the Hugo documentation conflates template selection and URL management, but this is unnecessary. It is advantageous to distinguish clearly between these two topics and discuss them separately.

Template Composition

Hugo templates make use of the templating package in the Go standard library. This package is extensively documented elsewhere. Check the Go Package Documentation or this series of tutorials.

The Hugo template for a page can be composed from smaller fragments.

  • Partial templates are template snippets that can be included in other pages.

  • A baseof.html template is a wrapper that can provide infrastructure common to all pages. A baseof.html template represents a complete page (but possibly without guts).

The primary difference between these two mechanisms is the lookup order: partial templates live in the partials/ subdirectory of the layouts/ directory. When a page template (such as list.html or single.html) is evaluated, it sucks in the appropriate partials. By contrast, the baseof.html template lives in layouts/_default/ or one of the subdirectories mirroring the page hierarchy. When a page template is invoked and a baseof.html exists, the base template is evaluated first, which then invokes the actual page template. (Both page and base template can invoke partials themselves.)

Shortcodes

Hugo shortcodes are a way to inject template commands into the plain text content. They are passed to the template and evaluated with it. Shortcodes are a way to send content-specific formatting options to the template. In effect, shortcodes are a way to circumvent Markdown’s limited markup capabilities, and to reflect the far richer possibilities of HTML.

Technically, shortcodes are template snippets stored in layouts/shortcodes/. The filename of the snippet, without the extension, becomes the shortcode command. For example, a shortcode stored in a file img.html would be invoked using {{< img />}}.

Shortcodes can take parameters that are available when the shortcode is expanded during template evaluation. Parameters are listed after the shortcode command and can be either positional or preceded by a keyword using key=value syntax.

As said earlier, shortcodes are a way to inject HTML formatting into content files. For example, the following trick keeps being rediscovered on the Hugo mailing list (but has only very recently made it into Hugo’s standard release). Imagine you would like to style some piece of content using a CSS class. Create a file layouts/shortcodes/div.html with the following content:

<div class="{{ .Get 0 }}">
{{ .Inner }}
</div>

and then use it within a Markdown file like this (callout is supposed to be the name of a CSS class, defined in a suitable stylesheet):

{{< div callout >}}
Some content...
{{< /div >}}

Finally, Hugo recognizes two different forms of invoking a shortcode:

  • {{< img />}} means that the “content” of the shortcode will be passed through to the template directly, it will not be parsed and rendered as Markdown.

  • {{% img /%}} means that the “content” of the shortcode will be treated as Markdown, and be parsed and interpreted accordingly.

The exact behavior of shortcodes has changed in different versions of Hugo; check the documentation for details.

Part 5: Odds and Ends — Tags, Content Summaries, Menus

Hugo provides some additional features, mostly to organize, summarize, and present content.

Adding Tags to Content

Hugo supports tagging content with keyword “tags”, in order to select and display all pages that have been tagged with some term. The Hugo documentation uses the term “taxonomy” for this functionality, and unfortunately makes a total hash of explaining what they are. That’s too bad, because it’s really very simple.

A “taxonomy” is simply a map (Hashmap, Dictionary, associative array). The keys in this map are strings, the values are ordered lists of pages.

That wasn’t so hard, was it?

Here is an example of a taxonomy, for simplicity rendered as JSON:

tags: {
  "Linux": [ "page1.md", "page2.md", "page3.md" ],
  "Ubuntu": [ "page3.md", "page1.md" ]
}

Tags are assigned to content in the content’s frontmatter. For example, the frontmatter of the file page3.md in the code sample above might include the following lines:

tags:
  - "Linux"
  - "Ubuntu"

It’s all very simple and straightforward.

Because a Hugo taxonomy is simply an internal data structure, themes can generate pages displaying taxonomy terms and the content associated with them (for example, displaying a list of all pages tagged with “Linux”).

Hugo ships with two taxonomies ready to use. (Remember that a Hugo “taxonomy” is simply an instance of a Hashmap.) They are called “tags” and “categories”. There is no difference between them, the names are arbitrary, you can use them in any way you like. However, themes may attach specific semantics to either and render them differently. Check the documentation for the theme of your choice.

Finally, it is possible to create additional “taxonomy” instances in the global configuration file. They can then be used in templates exactly like the built-in taxonomies.

Two concluding remarks:

  • Be aware that, although the term “taxonomy” usually implies a hierarchical ordering, Hugo’s taxonomies are “flat”: there is no nesting of taxonomy terms.

  • The Hugo documentation often refers to “adding a taxonomy to content”, but that is not what’s happening. Instead, the content is added to the taxonomy (remember that a taxonomy is a Hashmap).

Like “taxonomies”, menus are primarily another internal data structure that templates can access. Basically, a menu is an array of URLs. The template can then render this collection of links as a graphical “menu”.

A piece of content can add itself to a menu (through a frontmatter entry). Alternatively, menu entries can be made in the global configuration file.

It is possible to add directories to a menu. The menu entry will link to the directory’s “list” page that will display the items contained in the directory.

As with taxonomies, it is possible to have multiple instances of this data structure, and hence multiple, independent menus.

Content Summaries

Hugo has the notion of content “summaries” that a theme may display. For example, one can think of a “list” page, showing not only the title of each post, but also a brief summary of its contents.

There are three ways to define the “summary” for each piece of content:

  • If the content contains the separator <!--more--> (exactly like this), then all content up to that separator constitutes the summary.

  • Alternatively, the summary may be defined in the frontmatter, using the summary: key.

  • Lastly, the length of the summary (in words) can be defined in the global configuration file, using the summaryLength key.

It is not possible to switch off summaries by setting the summary length to zero, or by leaving the frontmatter entry blank. But placing the separator first in the content file (right after the frontmatter) does the trick.

Syntax Highlighting

Hugo uses the Chroma Go library for adding syntax highlighting to code samples. The generated HTML therefore does not have to rely on external JavaScript libraries. (A gallery of available styles can be found here.)

Unexpected Defaults

Sometimes the default behavior of Hugo is not what one would expect. Moreover, the Hugo documentation may not reflect changes in Hugo’s default behavior, leading to even more confusion. A few points that seem to cause frequent confusion include:

  • The default Hugo “archetype” includes a draft: true line in its frontmatter. By default, Hugo (silently) discards content that is labelled as draft. If some new piece of content fails to be processed, check for this first! (I recommend getting rid of the entire draft entry in the frontmatter entirely; it’s just too rich a source of confusion. Once in production, one may want to introduce it again, as a way to structure the workflow. But during set-up and experimentation, it is an unnecessary nuisance.)

  • Recent versions of Hugo do not pass embedded HTML through to downstream processing, but instead discard it. (This is also true for shortcodes that have expanded into HTML.) To allow embedded HTML, it is necessary to add the following to the global configuration file:

    [markup]
      [markup.goldmark]
        [markup.goldmark.renderer]
          unsafe = true