Adding JSON Feed to My Hugo Static Site


(Updated: )

I decided to implement JSON Feed on my website just for the hell of it. I do plan on having a better way of laying out my feeds but that’s for another day.

Configuring The Page For JSON Output

I first configured the home page to output JSON.

#config.toml
[outputs]
    home = ["HTML", "RSS", "JSON"]

Making the Template

I then made the template. There were two parts to this– getting the feed’s information and getting the information specific to each entry.

Feed’s Information

To get the feed’s information, I did this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<!--page.json-->
{
    "version": "https://jsonfeed.org/version/1",
    "title": "{{ .Site.Title }}",
    "home_page_url": "{{ with .OutputFormats.Get "html" }}{{ .Permalink }}{{ end }}",
    "feed_url": "{{ with .OutputFormats.Get "json" }}{{ .Permalink }}{{ end }}",
    "description": "{{ .Site.Params.Description }}",
    "author": {{ dict "name" .Site.Params.Author "url" .Site.BaseURL "avatar" (printf "https://www.gravatar.com/avatar/%s" (md5 .Site.Params.email)) | jsonify}},
    "items": [
        {{ $count := newScratch }}
        {{ $count.Add "count" 0 }}
        {{- range first .Site.Params.rssLimit (where .Site.Pages "Section" "blog") -}}
            {{- partial "page.json" . -}}
            {{- $count.Set "count" (add ($count.Get "count") 1) -}}
            {{- if ne ($count.Get "count") .Site.Params.rssLimit -}},{{ end -}}
        {{- end -}}
    ]
}

Note line 7 which is the author’s information, which consists of a name, url, and avatar. Using the dict and jsonify function, I was able to turn a list of fields into a valid JSON object. That results in something like this,

"author": {
    "avatar":"https://www.gravatar.com/avatar/6d2bd7cabb1639ba65863c24b4e6b87f",
    "name":"Farai Gandiya",
    "url":"https://fgandiya.me"
}

Isn’t in the order I would like, but it’s valid.

Also, in the items field, I had to create a $count variable. What $count does is make sure that the last element in the page list1 doesn’t have a trailing slash. I thought I could get away with it, but JSON doesn’t allow for trailing commas.

Item’s Information

As for each item’s information, I put the logic into a partial called page.json.

{{ $info := newScratch }}
{{ $info.SetInMap "info" "url" .Permalink }}
{{ $info.SetInMap "info" "id" .Permalink }}
{{ $info.SetInMap "info" "title" .Title }}
{{ $info.SetInMap "info" "content_html" .Content }}
{{ $info.SetInMap "info" "content_text" ( .Content | plainify ) }}
{{ if isset .Params "description" }}
    {{ $info.SetInMap "info" "summary" .Params.description }}
{{ else }}
    {{ $info.SetInMap "info" "summary" .Summary }}
{{ end }}
{{ $info.SetInMap "info" "date_published" .Date }}
{{ if ne .Date .Lastmod }}
    {{ $info.SetInMap "info" "date_modified" .Lastmod }}
{{ end }}
{{ jsonify ($info.Get "info") }}

Working off of how I made the author object, I thought I would be able to create a dict object and dynamically append to that. That didn’t work. After hours of Googling about “how to create an object in Hugo”, I got nothing. closest thing I found was a blog post on how to Build a JSON API With Hugo’s Custom Output Formats by Forestry.io.

From there, I picked up that I had to make a Scratch. Using the .SetInMap method, I was able to add each entry to a key called info, that uses a map as its value. With that map, I could then build up the post’s information before I turned it into a JSON object with jsonify.

The beauty of jsonify is that it escapes everything that you need to escape. This is really handy when it comes to the content_html field that uses .Content which outputs HTML.


  1. Made up of .Site.Params.rssLimit items set to 15 for my site.