Hugo shortcodes with markdown, gotchas

Hugo shortcodes with markdown, gotchas

Generally Hugo works intuitively, unfortunately not for shortcodes with markdown text inside. While the documentation is often rather minimal, in this case it is incomplete. Having spend a lot of time wondering what i was doing wrong eventually a forum post a on the Hugo forums made everything fall in place. So to save you the effort i have summarized my findings here. This post only applies to Hugo 0.55 and beyond, at the time of writing i’m using 0.72.

Click here to go straight to the solution.

The problem

You will notice if you use shortcodes with markdown content the text might not always be properly converted to markdown when using the {{< MyShortcode >}} or {{% MyShortcode %}} syntax. What gives? You can use markdownify on the content in your shortcode and it will render mark-up but not footnotes (and sometimes not links). Given there is not much one can configure and the documentation is not helpful this issue can become a time sink real quick followed by frustration and lots of trial and error.

Note: Hugo has 2 markdown engines: Blackfriday and Goldmark, the latter is enable by default and newer; i have only used that one.

The documentation

First let’s see what the shortcode template documentation says:

For example, layouts/shortcodes/myshortcode.html will be called with either {{< myshortcode >}} or {{% myshortcode %}} depending on the type of parameters you choose.

- retrieved 18-07-2020

even without parameters the issue ramins so that is not helpful. If you go to the shortcode variable documentation it does not even mention the % or <> syntax.

From the general shortcode documentation:

In your content files, a shortcode can be called by calling {{% shortcodename parameters %}}.

[example with % and <> syntax is provided]
The examples above use two different delimiters, the difference being the % character in the first and the <> characters in the second.

Shortcode with markdown

In Hugo 0.55 we changed how the % delimiter works. Shortcodes using the % as the outer-most delimiter will now be fully rendered when sent to the content renderer (e.g. Blackfriday for Markdown), meaning they can be part of the generated table of contents, footnotes, etc.

If you want the old behavior, you can put the following line in the start of your shortcode template: {{ $_hugo_config := { "version": 1 } }}

Shortcodes without markdown

The < character indicates that the shortcode’s inner content does not need further rendering. Often shortcodes without markdown include internal HTML:

{{< myshortcode >}}<p>Hello <strong>World!</strong></p>{{< /myshortcode >}}

- retrieved 18-07-2020

This is more helpful. We want markdown rendered so have to use %. Unfortunately it will not always work (including the most common use-case, see later), and trying to enable to old behavior does not help either (the documentation is also lacking to specifying exactly what said “old behavior” is).

Background on why they have added <> (originally Hugo only had %):

There is a history here. Once we had only {\{% and it behave like it does today (more or less). But there where all kinds of issues reported with how the different markdown processors handled inline HTML etc. in markdown. So, beyond the very simple examples, if you want to preserve HTML as … HTML without any encoding/formatting issues, you want to use the {\{<.

- bep May 2019

The reason

To further understand why % does not always work as expected this post on the Hugo forums explained it all:

If using the {{\%-syntax it will work as expected as long as you don’t have surrounding html-elements in your shortcode (black friday will not parse markdown inside of html-elements). But because it is usual to have html in the shortcode you have to work around it, see Shortcode - markdown vs html vs table of content for a possible solution.

- Iar May 2019

So we must avoid passing markdown content wrapped in a HTML tag, which is unfortunately the most common use case and no where specified in the documentation.

Summary shortcodes using <> their content will not be passed to the markdown renderer, when using % the content will be passed to the markdown renderer BUT it will ignore any content wrapped in HTML elements. If you manually call the markdown renderer in your shortcode (markdownify) footnotes will not work; only markup and links.

The solution

Without further or due, the shortcode (all credit goes to zwbetz):

{{ $type := .Get 0 }}
{{ printf "<blockquote class=\"md-hint %s\">" $type | htmlUnescape | safeHTML }}
{{ .Inner }}
{{ printf "</blockquote>" | htmlUnescape | safeHTML }}


{{% hint %}}
Example *with* formatting **and** footnotes[^note].
{{% /hint %}}
[^note]: show me!


Example with formatting and footnotes1.

so we avoid the HTML elements to be passed to the markdown engine which further processes the inner content to markdown and everything finally works!

If you use % do not trim the inner content ({{- .Inner -}}) or footnotes will break once more. This is another bug.

If you only care about mark-up, and not footnotes you can use <> in combination with {{ .Inner | markdownify }} e.g. <blockquote class="md-hint {{ .Get 0 }}">{{ .Inner | markdownify | safeHTML }}</blockquote>

Hugo also has a problem with processing markdown right after a shortcode (applies to both % and <>) e.g.

{{% /hint %}}
So here the `text` with [markup]( continues and will be broken.

the solution is using a blank line after the shortcode:

{{% /hint %}}

So here the `text` with [markup]( continues and will work as expected.

A personal pet peeve, you cannot force to use either <> or % for a shortcode in your markdown content. If you use the wrong one the shortcode will simply break (no errors though), this is leaking implementation details into the client side. A bad practice that can cause a lot of maintenance if the shortcode ever changes its required syntax.

Ideally how the shortcode works internally should be transparent to its usage in markdown.

While quite a few users reported this issue already mid 2019, so far the issue nor the documentation have been fixed. I hope this post will become obsolete rather sooner than later. There is an open issue to fix the superfluous paragraphs.

  1. show me! ↩︎

Noticed an error in this post? Corrections are appreciated.

© Nelis Oostens