Custom content types in Hugo

16 Jul 2018 in Tech

One of the things that attracted me to Hugo was it's strong custom content type support. It took me a little while to understand that types in Hugo are based on the file structure. If you wanted to create a new content type of dog, you just start adding content to the content/dog folder. Let's create a dog named Desmond now using Hugo:

bash
hugo new dog/Desmond.md

This will create content/dog/Desmond.md with the following content:

yaml
---
title: "Desmond"
date: 2018-07-15T21:46:09+01:00
draft: true
---

This pre-filled content comes from an archetype, which is the template that Hugo uses when creating a new piece of content. It doesn't make sense for a dog to have a title, date or be in draft. However, it does make sense for a dog to have a name and either be a good_dog or not. To create a new archetype, create a file with the same name as your content type in archetypes. In this case, create archetypes/dog.md with the following content:

yaml
---
name: "{{ .Name }}"
good_dog: true
---

This uses the .Name field from Hugo (which is the name of our file, Desmond) and says that the dog is a good_dog (because aren't all dogs good dogs?)

Delete content/dog/Desmond.md and then create him again using hugo new dog/Desmond.md. If you inspect the contents of content/dog/Desmond.md now you'll see that it follows our dog archetype template:

yaml
---
name: "Desmond"
good_dog: true
---

List all of our dogs

When creating a new type, you can provide a custom template that lists entries of that type by creating an list.html file. Let's do this for our dogs by creating layouts/dog/list.html with the following content:

html
<h3>Dogs</h3>
Dogs list to be added soon

When Hugo runs, it searches for a matching template file for the current category following the Hugo template lookup rules. This means that it will use layouts/&lt;category&gt;/list.html if it exists, before falling back to layouts/_default/list.html. If neither of those exist, it'll look for the same files inside the current theme.

Create layouts/dog/list.html with the following contents:

html
{{ define "main" }}
<h3>Dogs</h3>
<ul class="posts">
{{ range .Data.Pages }}
<li>{{ .Params.name }}</li>
{{ end }}
</ul>
{{ end }}

In Hugo all of the data is scoped to the current context. This means that .Data.Pages only contains pages in the dog directory when we visit http://localhost:1313/dog/. When looping over the pages the metadata on the post is available in the .Params variable, allowing us to access the name of our dog as .Params.name.

Once you've created this file, visit http://localhost:1313/dog/ to see a list of your dogs rendered.

Listing a page on the homepage

The final thing I wanted to do was add a list of dogs to the home page. I started by copying the loop from our layout/dog/list.html page which rendered all of the dogs, but also rendered all other pages on the site. This is because on the home page, the scope of .Data.Pages contains all types.

html
{{ define "main" }}
<h3>All our dogs</h3>
<ul>
{{ range .Data.Pages }}
<li>{{ .Params.name }}</li>
{{ end }}
</ul>
{{ end }}

To make it render only dogs we have to add a where condition to our template. We can ask Hugo to loop over .Data.Pages where the Section is equal to dog and only output those pages.

html
{{ define "main" }}
<h3>All our dogs</h3>
<ul>
{{ range where .Data.Pages "Section" "dog" }}
<li>{{ .Params.name }}</li>
{{ end }}
</ul>
{{ end }}

After saving this and reloading the home page we see that there is a list of dogs (and only dogs) displayed.