Creating a JSON Feed for Hugo

This post is more than 2 years old.

Edit on 3/26/2018 to fix tags, thanks to commenter Carl Recently a new specification was launched to recreate RSS in JSON, JSON Feed. For folks who may not be aware, RSS is an XML spec (well, multiple ones) for sharing content between sites. Blogs, primarily, and content-heavy sites typically make use of this. I'm not sure how many people outside of developers actually use RSS, but it's still definitely a "thing" even if you don't necessarily think of it when thinking about APIs.

The idea behind JSON Feed is to simply recreate the same, or similar, functionality in JSON as opposed to XML. You can read more about the launch on their announcement post and read the full spec as well. (And actually, this is one of the better written specs I've seen. You won't need a PhD in CompSci to grok it.)

I thought it might be fun to build a JSON feed for my Hugo site. I ran into some trouble getting the JSON output working right and want to thank @bep for helping me out on the forums. I based my solution on a new Hugo feature, Output Formats, but obviously I may have done this in a completely stupid manner as well. As always, I encourage folks to leave me a comment and let me know what could be improved.

ALright, so to begin, I created a file in /content called jsonfeed.md. This file is empty and just serves as a way to tell Hugo where to create the feed.

{
	"outputs":["json"],
	"layout":"feed"
}

Note that I'm specifying json as my output and I'm saying my layout will be the feed template. That's where the real work takes place:

{
    "version": "https://jsonfeed.org/version/1",
    "title": "{{ .Site.Title }}",
    "home_page_url": "{{ .Site.BaseURL }}",
    "feed_url": "{{ .Permalink}}",
	{{ if isset .Site.Params "description" }}
	"description": "{{ .Site.Params.description }}",
	{{ end }}
	{{ if isset .Site.Params "author" }}
	"author": { "name": "{{ .Site.Params.author }}" },
	{{ end }}
    "items": [
    {{ range $i, $e := first 10 .Site.Pages }}
		{{ if $i }}, {{ end }}
			{
				"id": "{{ .Permalink }}",
				"title": "{{ .Title }}",
				"content_text": {{ .Summary |{% endraw %} jsonify }},
				"url": "{% raw %}{{ .Permalink }}",
				"date_published": "{{ .Date }}",
				"tags": ["{{ delimit .Params.tags "," }}"]
			}
		{{ end }}
    ]
}

Most of this should make sense I think - but let me call out some particular aspects. First, I've made description and author both optional based on whether or not the user has this in their Hugo settings.

In the range call, I had to use slightly weird syntax to support "include a comma except for the last iteration." This syntax makes zero sense to me, but I copied it from another support post by @bep located here. Even though the internal if with the comma is at the top, it renders at the bottom. Yeah, ok.

As for each item, for the most part it just plain works as shown. Hugo summaries are plain text only so I used content_text instead of content_html. I run it through a pipe to make it safe for JSON. For tags, I was a bit torn. Hugo supports both categories and tags, and in theory you could use both I guess. Or just categories. I used tags just to keep it simple, but that's definitely something you would want to look into for your own Hugo site.

Finally, the date one rendered a bit weird on my blog. Let's look at the output to see the full response (I removed a bunch of items to make it shorter):

{
    "version": "https://jsonfeed.org/version/1",
    "title": "Raymond Camden",
    "home_page_url": "http://localhost:1313/",
    "feed_url": "http://localhost:1313/jsonfeed/index.json",
	"author": { "name": "Raymond Camden" },
	
    "items": [
		
			{
				"id": "http://localhost:1313/2017/05/18/creating-a-json-feed-for-hugo/",
				"title": "Creating a JSON Feed for Hugo",
				"content_text": "Recently a new specification was launched to recreate RSS in JSON, JSON Feed. For folks who may not be aware, RSS is an XML spec (well, multiple ones) for sharing content between sites. Blogs, primarily, and content-heavy sites typically make use of this. I\u0026rsquo;m not sure how many people outside of developers actually use RSS, but it\u0026rsquo;s still definitely a \u0026ldquo;thing\u0026rdquo; even if you don\u0026rsquo;t necessarily think of it when thinking about APIs.",
				"url": "http://localhost:1313/2017/05/18/creating-a-json-feed-for-hugo/",
				"date_published": "2017-05-18 11:32:00 -0700 -0700",
				"tags": ["hugo"]
			}
		
		, 
			{
				"id": "http://localhost:1313/2017/05/15/my-own-openwhisk-stat-tool/",
				"title": "My Own OpenWhisk Stat Tool",
				"content_text": "While waiting at the airport this past weekend, I worked on a little utility to help me retrieve information about my OpenWhisk actions. As you know (hopefully know!), Bluemix provides a \u0026ldquo;stats\u0026rdquo; page for your OpenWhisk stuff but it is a bit limited in terms of how far it goes back and doesn\u0026rsquo;t yet provide good aggregate data about your action. So for example, I really wanted to see how well my action was responding in a simple tabular fashion.",
				"url": "http://localhost:1313/2017/05/15/my-own-openwhisk-stat-tool/",
				"date_published": "2017-05-15 09:22:00 -0700 -0700",
				"tags": ["openwhisk"]
			}
		
		, 
		
			{
				"id": "http://localhost:1313/2017/04/28/bound-packages-openwhisk-and-web-actions/",
				"title": "Bound Packages, OpenWhisk, and Web Actions",
				"content_text": "Hey folks, this is just a warning to other users in case they run into the same issue I did. As you (may) know, OpenWhisk supports the idea of packages. Packages let you organize actions into a cohesive unit, much like packages in other languages/platforms. Packages can also have default parameters that apply to every action in the package.\nPackages can also be shared, which makes them callable by other users.",
				"url": "http://localhost:1313/2017/04/28/bound-packages-openwhisk-and-web-actions/",
				"date_published": "2017-04-28 09:08:00 -0700 -0700",
				"tags": ["openwhisk"]
			}
		
    ]
}

Notice the double -0700 -0700? I'm pretty sure that's just an issue on my blog due to how I specify dates in my posts. I probably did it wrong, but with near 6000 posts, I'm not changing it. For my fix, I switched up the above code to:

"date_published": "{{ replace .Date "-0700 -0700" "-0700"}}",

And that's it. As I said, there's probably a better way of doing this, and I honestly don't know if anyone is going to even make use of JSON Feed, but if folks want mine, they can find it at https://www.raymondcamden.com/jsonfeed/index.json

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Antonio Gallo posted on 5/18/2017 at 10:10 PM

Interesting. I think i'm one of the few survivers still using RSS to follow the most interesting blogs instead of becoming crazy into the social media information overflow.

Comment 2 (In reply to #1) by Raymond Camden posted on 5/18/2017 at 10:12 PM

I was using some RSS reader a while back, but I literally forgot I had it. :\

Comment 3 by Raymond Camden posted on 5/19/2017 at 1:41 AM

Oops - looks like I forgot my online Hugo is a bit behind my local Hugo. That URL won't work till I fix that.

Comment 4 (In reply to #3) by Raymond Camden posted on 5/19/2017 at 2:02 AM

Fixed.

Comment 5 by Ananda Yudy posted on 2/27/2018 at 9:30 PM

this is what i'm looking for... is it possible to have json from each content? with final output blog.com/myarticle-title/my...

Comment 6 (In reply to #5) by Raymond Camden posted on 2/27/2018 at 9:59 PM

No idea. :) To be honest, I find so much of Hugo difficult to work with I wish I could change. :\

Comment 7 (In reply to #5) by Carl posted on 3/26/2018 at 7:16 PM

Yes. In layouts / _default make single.json and that will be the template for the JSON version of individual posts.

Comment 8 by Carl posted on 3/26/2018 at 7:18 PM

Your tags are wrong. It is supposed to be an array of strings, i.e. "tags": ["RSS", "JSON", "Hugo"]. Please correct the post because it is very likely that people will copy/paste the post and it will lead to tags being wrong in many places…

Comment 9 (In reply to #8) by Raymond Camden posted on 3/26/2018 at 7:30 PM

To be honest, I completely forgot this was even a thing. I guess people are using JSONFeed which is good. ;) I'll look into correcting it.

Comment 10 (In reply to #9) by Raymond Camden posted on 3/26/2018 at 7:40 PM

Corrected.

Comment 11 (In reply to #7) by Ananda Yudy posted on 3/30/2018 at 11:48 PM

thank you Carl :) I'll try add single.json template then.

Comment 12 (In reply to #10) by Carl posted on 4/3/2018 at 7:39 PM

Awesome! Thanks!

Comment 13 by Drew Norman posted on 4/11/2020 at 4:15 AM

So did all the code get fixed. I need a reliable json solution. I am looking to migrate my hugo site to Wordpress.

Comment 14 (In reply to #13) by Raymond Camden posted on 4/11/2020 at 3:19 PM

Did what code get fixed?

Comment 15 (In reply to #14) by Drew Norman posted on 4/12/2020 at 2:29 AM

Json Feed.

Comment 16 by Drew Norman posted on 4/12/2020 at 2:31 AM

I want to export all my Hugo sites into one Json dump so that I can use a Json to Wordpress plugin to migrate all text data into WP.

Comment 17 (In reply to #15) by Raymond Camden posted on 4/13/2020 at 12:24 AM

It wasn't broken, was it?

Comment 18 (In reply to #16) by Raymond Camden posted on 4/13/2020 at 12:24 AM

You could try modifying my code. Removing the First filter should return all the content.