Why doesn't Hugo use Liquid

Obviously since Hugo is written in Go language, it’s using Go Templates. However, I’m curious about the decision-making on this relative to using Liquid for templating instead.

I’m very fresh to Hugo and SSGs generally, so it may be useful to know some of what I’ve noticed:

  1. When I go to Liquid docs, I like what I see and find what I’m looking for quickly. When I go to Go templates page… I’m totally lost. I scroll for a bit, then give up and return to Hugo documentation or Google.
  2. When I first saw {{ block "main" . }} I thought the period was a typo. I removed the period and wasn’t aware that Hugo failed to regenerate. This took me quite a while to realize that the period was required. While I understand why it has to be there now, it was not intuitive and the error wasn’t readily apparent when I saved the file without the period.
  3. A significant number of the themes I took a look at could really use some help with DRY principles. I’m glad there is a decent selection, but once I start diving in I’m surprised to find code that is unnecessarily complex or repetitive. This is somewhat expected in any theme library, but I was really hoping for a better starting point that I could adapt to my own. Maybe have a small handful of “featured” themes? Instead, it looks like I need to start entirely from scratch just to get a well structured theme. I suspect the challenge of the Go Templates may be a factor driving down the quality of the themes.

So this leads to my question… why doesn’t Hugo use Liquid or at least offer it as an option?
There are ports of Liquid in Go… notably osteele/liquid.

  • Perhaps liquid is not as efficient?
  • Perhaps liquid is not as robust?
  • Perhaps it’s too risky to depend on a third party library?

I’d really appreciate hearing thoughts and reasoning on this. Thank you.

I am not a Go Dev but the Liquid port you linked to above had its latest commit 3 months ago.

The Go html/template package is native to Go and actively maintained by the Go team. Latest commit in the html/template package was in July

Also Liquid may be a terser templating language than Go templates but what is its performance? There is an issue about adding benchmarks in the Liquid Go port repo that is currently open.

It seems that it is a bit premature to have this discussion about incorporating Liquid templates in Hugo.

Thanks for noticing that. It looks like the last update to Liquid was may 15 and the last update to the Osteele port was june 5. So it seems like the port is updating right along with Liquid.

Your question on performance is my question too! Good thoughts.

Maybe because Liquid is HORRIBLE! :confounded: That is one of the major reasons I quickly ditched Jekyll. Of course, their implementation of Liquid is even more horrible than the original. You can see some of my ranting here:

I think (and hope) that this is irrelevant: Hugo users should not have to read Go documentation to understand Hugo.

Is there something missing from Hugo’s own template documentation page that you hoped to find in the Go documentation? If we know what’s missing we can add it. :slight_smile:


When I first saw {{ block “main” . }} I thought the period was a typo. I removed the period and wasn’t aware that Hugo failed to regenerate. This took me quite a while to realize that the period was required. While I understand why it has to be there now, it was not intuitive and the error wasn’t readily apparent when I saved the file without the period.

Juste have a look here to understand the period / dot: https://regisphilibert.com/blog/2018/02/hugo-the-scope-the-context-and-the-dot/

1 Like

You should first go through the Go Template Introduction in Hugo docs:

That covers the dot too. If you would like more details, refer to the blog post by @regis that @nfriedli linked.

Go Templates are awesome! You’ll start appreciating the dot once you get it. :slight_smile:


Well, I know nothing at all about Go. But I can say this. When I first started using Jekyll, it took me a lot longer to get something working than it did in Hugo. In Hugo, I quickly ditched the idea of using someone’s template as none fitted what I wanted (something similar to the Wordpress 2016 theme). Writing a Hugo template from scratch was remarkably straight forwards.

It is true that some of the documentation requires about 5 reads before I understand it. But that is more about documentation style than Hugo utility or ease of use. Of course there are oddities, all frameworks have oddities, but Hugo’s seem a lot less than competitors. But that is only my view of course :slight_smile:

Jekyll’s use of Liquid drove me absolutely up the wall in no time at all and I found it far more complex to use and very inconsistent in places as my ranting blog post will indicate.

With Hugo, I have found very few inconsistencies and I’ve also found it to be very robust overall, another issue with Jekyll.

Not trying to knock Jekyll, I know a lot of people use it. But it wasn’t for me. I looked at and tried a lot of options when searching for alternatives to Wordpress. I’m more of a Node.JS person so my main aim was that and I was very reluctant to try something based on GoLang. Glad I did though, not looked back since. Still always hoping that I never have to learn GoLang though! One bonkers language (JavaScript) is enough for me!!

Since I really want to use Hugo for speed, I hope my detailing the templating problems will help bring improvements. As a long time front end developer, but a Hugo novice, here’s how it went for me reading Hugo’s Introduction to templating:

The following is only a primer on Go templates. For an in-depth look into Go templates, check the official Go docs.

Immediately I’m thinking I should be reading the Go website instead of Hugo’s tutorial… but when I visit Go’s site, I read and read and read… then finally come back to Hugo’s site.

Access a Predefined Variable

{{ foo }}

I really wish there was a normal “hello world” or real example here. It isn’t clear what a “predefined” variable is. Does “predefined” mean a “global”… or something built into GoLang or Hugo… or something simply defined in the build… dunno so I keep going…

This is how you access a Page variable from a template:

<title>{{ .Title }}</title>

Values can also be stored in custom variables and referenced later:

{{ $address := "123 Main St."}}
{{ $address }}

It isn’t clear why one is accessed from dot notation why the $ was introduced. Why not treat everything as an object? Already I am sensing an inconsistency that will be confusing.

Example 2: Comparing Numbers

{{ lt 1 2 }}

Again confused… is this a hint that operators are not available? what about {{ 1 < 2 }} instead? Also what does this look like in the context of a web page? Does this statement print true or does it evaluate as true for an if statement?


To pass along the current context, please remember to include a trailing dot .


{{ partial "header.html" . }}


{{ template "_internal/opengraph.html" . }}

At this point I’m getting really confused. What is the difference between “include” (which has no example), “partial” and “template” This seems like all the same concept and should use the same notation. I’m now afraid building templates will be very tedious from constant semantic issues.

 {{ range array }}
    {{ . }}
{{ end }}

I have no idea what I’m supposed to understand from this block.

By default, Go Templates remove HTML comments from output.

Why would it do this unexpected behavior? Commenting is already available via {{/* comments here... */}}

Go 1.6 includes the ability to trim the whitespace from either side of a Go tag by including a hyphen ( - ) and space immediately beside the corresponding {{ or }} delimiter.

Why in the world would a newline be included that I didn’t request? Immediately I think it should say: "write all your code blocks as {{- .Title -}} to avoid random newlines.

I’m sure there are good reasons for this and I’ve now read a bunch about it. Hugo’s documentation is decent too. Unfortunately, this is exactly what I (and probably most front-end devs) don’t want to do. The following simply is not intuitive when I compare it to other systems:

( .Params "title")
$title := .Site.Title
{{ with .Params.title }}{{ . }}{{ end }}


Again, the point of all this isn’t to bash Hugo or GoLang. The point is to illustrate how confusing this is for a template developer to get up and running so Hugo can be a bigger success! Parts may be related to GoLang and not specific to Hugo. However, I’m not looking to become a Go developer (I’m not interested in the details). Like most devs; I’m simply wanting to build a front-end effectively and efficiently (read: we are LAZY). The only time I’ve used liquid in the past was on Shopify sites that I’ve built, and it was up and running instantly. I’d love for Hugo to be that easy. If I could choose my template engine in Hugo, I’d rather have one that doesn’t take so much orientation time… even if it cost me a small performance hit in the compilation time.

There are many things that Hugo does well, such as the built in server and livereload.


Let me try to explain:

( .Params "title") : A front matter parameter called title. The shorthand syntax is .Params.title

$title := .Site.Title A custom variable that holds the global .Site.Title variable typically set in the config of a Hugo project

.Title The default title variable of any page in Hugo. If not set via a .Params "title" in the front matter of a markdown file or via the global .Site.Title it will default to the PATH of a page within the hierarchy of a Hugo project.

All of the above seem the same but they’re not quite the same.

Each one has its use case.

1 Like

Thank you!

  1. I’m still not clear what the difference is between .Params.title and .Title?
  2. Why is “title” capitalized with the .Site object and not with .Params?
  • .Params is a map/dictionary with key/values provided by the user
  • .Title and similar are built-in fields or methods in Hugo. They are capitalized because that is the Go way of exporting fields/methods. Of course, .Title will also get its value from the user some how, but in some cases we create some default value or it is a calculated value etc.

That’s some useful feedback. I have tried to cover most of your addressed points, and have refreshed that “Introduction to Hugo Templating”:


MUCH appreciated. Your commit really helps clarify a lot of my questions. I’ll read more on this, but thank you very much for this progress!

In a way, a lot of @bristweb’s comments about the docs speak to this issue: