Hugo Templating Mini-Guide and Cheat Sheet

Jesse Barkdoll
Updated: March 25, 2024 Published: March 18, 2024

Hugo templates are based on the go-html-template/template and text/template Go libraries. Most, if not all reference documentation for these libraries is applicable to Hugo as well.

Topics covered in this article:

Context

Context is the data passed to a template. Current context of a template is always represented with a dot (.) and can be used like so:

<p>Hi my name is {{ .Name }}</p>

Changing Context using range and with blocks

The current context (.) changes inside of range and with blocks to be the specified range or value.

Example

Let’s say you have a product listing post (markdown file) in your Hugo site with the following frontmatter: cheese.md

params:
    product:
        id: 32
        label: 'Sharp Cheddar'
        price: 2.99

And in your Hugo post template, you have the following:

layouts/_default/single.html
{{ with .Params.product }}
    <span id="product-{{ .id }}">{{ .label }} (${{ .cost }})</span>
{{ end }}

RESULT: the template should parse to the following snippet when you build the site.

<span id="product-32">Sharp Cheddar ($2.99)</span>

Blocks

Blocks are reusable chunks of Hugo/Go template markup.

There are 2 types of template files.

We can define blocks in base templates, and then compose them together in a site template (and optionally override their base template content).

layouts/_default/baseof.html
<main>
{{ block "main" . }}
  <span>I AM THE DEFAULT</span>
{{ end }}
</main>

Here we have defined the “main” block’s location in a base template and given it default content when no content is rendered for this block in an inheriting site template. Notice how the context dot (.) is at the end of the block definition open statement.

Global Site Data in Templates

We can access data related to the site’s main configuration using .Site in a template like so:

{{ with .Site.Author }}
    <p>This site was built by {{ . }}</p>
{{ end }}

Properties available in .Site (referenced like .Site.<property>, example .Site.AllPages)

Custom Data Sources

Data does not always need to be input into markdown post frontmatter.

What if you wanted to use an isolated set of data on your site for some purpose?

Well, there is the data/ directory for this exact reason. You can add static data from any of the supported formats.

Supported Formats

If you had the following file in your Hugo project’s data/ directory…

data/my-magnets.yaml
Title: 'My Magnets'
Collection:
- 'magnet 1'
- 'magnet 2'
- 'magnet 3'
- 'magnet 4'

…you can bind it in context using range/with blocks and the index function in a template like so:

{{ with index .Site.Data "my-magnets" }}
<span>{{ .Title }}</span>
<ul>
    {{ range .Collection }}
    <li>{{ . }}</li>
    {{ end }}
</ul>
{{ end }}

Result

<span>My Magnets</span>
<ul>
    <li>magnet 1</li>
    <li>magnet 2</li>
    <li>magnet 3</li>
    <li>magnet 4</li>
</ul>

Querying Pages for Lists (Iteration)

We can query for certain pages using an index or where function or aggregate pages into groups using a .GroupBy function. Then we can iterate through those pages and groups with range.

Here are some examples.

Get pages with "hobbies" as a tag

<h3>Posts related to Hobbies</h3>
<ul>
    {{ range (index .Site.Taxonomies.tags (lower "hobbies")).Pages }}
    <li>{{ .LinkTitle }}</li>
    {{ end }}
</ul>

Get pages that are “posts”

{{ $posts := where .Site.RegularPages "Type" "posts" }}
{{ range $posts }}
<div class="post">
    {{ .Title }}
</div>
{{ end }}

Sort them by date using .ByDate:

{{ range $posts.ByDate }}
<!-- ... -->
{{ end }}

Sort them by date in descending order using .Reverse:

{{ range $posts.ByDate.Reverse }}
<!-- ... -->
{{ end }}

Get pages published in the last 30 days

Here we wrap a base where statement in another where statement together to further refine results.

{{ $latest := where (
      where .Site.RegularPages "Type" "posts"
    ) 
    "Date" "ge" (now.AddDate 0 0 -30) 
}}
{{ range $latest.ByDate.Reverse }}
<div class="post">{{ .Date | time.Format "2006-01-02" }} / {{ .Title }}</div>
{{ end }}

This entire query handles the following:

Group pages by a custom parameter (in YAML frontmatter)

In this case, the parameter is product_category. Markdown files with a product_category param will be grouped by it’s value. So if we have a few posts with the params.product_category value set to 'kitchenware', they will be grouped here and you can iterate over them for display in your template.

params:
  product_category: kitchenware

And in our template:

<section>
    <div>Product Categories</div>
    {{ range .Site.RegularPages.GroupByParam "product_category" }}
    <span>{{ .Key | title }}</span>
    <ul>
        {{ range .Pages }}
        <li>{{ .Title }}</li>
        {{ end }}
    </ul>
    {{ end }}
</section>

There’s so much more…

There are many foundational concepts to cover within Go templates and their usage in Hugo. If there are any specific ones you’d like me to include with quick examples in this guide, feel free to ping me on X (formerly Twitter).