✨ How to Write a Hugo Shortcode

Hugo Shortcodes are reusable snippets to insert dynamic or complex content.

Introduction

Shortcodes make it easy to reuse the same logic or markup across your content. They help keep Markdown files readable while allowing you to insert dynamic or configurable elements when needed.

You should consider writing a shortcode when:

  • You need to reuse the same component in multiple places
  • You want to centralize or share data across different content files
  • You need to encapsulate complex or verbose HTML
  • You want to ensure consistent presentation across posts
  • You need to embed third-party content (videos, tweets, code snippets, etc.)
  • You want to separate content from layout and styling concerns
  • You need conditional rendering based on parameters or context
  • You want to provide simple, author-friendly building blocks instead of raw HTML

The Basics

To call a shortcode in your post, use its filename (without .html).

  • Use {{% %}} when the shortcode output contains Markdown that must be rendered
  • Use {{< >}} when the shortcode outputs HTML only (preferred for performance)

Examples:

  • {{% parameters params.yaml %}} # Markdown-aware
  • {{< parameters "params.yaml" >}} # HTML-only
  • {{% hugo/parameters params.yaml %}} # will look for ./layouts/shortcodes/hugo/parameters.html

In this posts, we already saw that we can use shortcode provided by the theme’s modules, but let’s write some customs.

Some use cases

Split code from the articles

On this blog, I often include large code blocks. Instead of embedding them directly in Markdown, it’s often cleaner to keep them next to the article - for example in a code/ or codes/ folder - and include them using a dedicated shortcode.

This approach makes the article easier to read and allows code snippets to be reused or updated independently of the content. Separating code from content keeps articles readable while letting code evolve independently. You can achieve this with a code-snippet shortcode.

  • Shortcode location: ./layouts/shortcodes/code-snippet.html

  • Content structure example:

1content/
2└─ posts/
3   └─ my-article/
4      ├─ index.md
5      ├─ codes/
6        └─ example.conf
  • The code:
 1{{- $name := printf "{code/%s,codes/%s}" ($.Get 0) ($.Get 0) }}
 2{{- $res := .Page.Resources.GetMatch $name }}
 3
 4{{- with $res }}
 5  {{- /* 1. Explicit language argument */ -}}
 6  {{- $lang := $.Get 1 | default "" }}
 7
 8  {{- /* 2. Fallback to file extension */ -}}
 9  {{- if not $lang }}
10    {{- $lang = replaceRE "^.*\\." "" .Name | lower }}
11  {{- end }}
12
13  {{- /* 3. Final fallback */ -}}
14  {{- if not $lang }}
15    {{- $lang = "txt" }}
16  {{- end }}
17
18  {{- highlight .Content $lang "" }}
19{{- else }}
20  {{- warnf "code snippet not found: %s" $name }}
21{{- end }}
  • The usage:

Language is taken from the file extension

1{{< code-snippet example.txt>}} 

Or you can declare the language

1{{< code-snippet example.txt ini>}}
  • The result:
    1[Test]
    2Description="Some random text to demonstrate the `code-snippet` shortcode."

Generate a Table from a yaml

You can generate a table from a configuration file using a shortcode. This is especially useful when you need to apply custom styling or effects, or when the dataset is large.

Instead of maintaining a long Markdown table, you can store the data in a a-long-list-of-stuffs.yaml file, which is easier to read and update over time.

So let do a shortcode which:
✔ Generate a valid table
✔ Auto-generates headers
✔ Makes URLs clickable
✔ Renders Markdown / HTML when present
✔ Supports YAML / JSON / TOML inputs

  • The code in ./layouts/shortcodes/table-snippet.html:
 1{{- $res := .Page.Resources.GetMatch (.Get 0) }}
 2{{- if not $res }}
 3  {{- errorf "table-snippet: file not found: %s" (.Get 0) }}
 4{{- end }}
 5
 6{{- $data := $res | transform.Unmarshal }}
 7
 8{{- /* Optional column order from shortcode */ -}}
 9{{- $headers := slice }}
10{{- with .Get 1 }}
11  {{- $headers = split . "," }}
12{{- end }}
13
14{{- /* Fallback: auto-detect keys if no order provided */ -}}
15{{- if eq (len $headers) 0 }}
16  {{- range $data }}
17    {{- range $k, $_ := . }}
18      {{- if not (in $headers $k) }}
19        {{- $headers = $headers | append $k }}
20      {{- end }}
21    {{- end }}
22  {{- end }}
23{{- end }}
24
25<table class="table table-bordered table-hover table-striped">
26  <thead>
27    <tr>
28      {{- range $headers }}
29        <th>{{ . }}</th>
30      {{- end }}
31    </tr>
32  </thead>
33  <tbody>
34    {{- range $data }}
35      {{- $row := . }}
36      <tr>
37        {{- range $headers }}
38          {{- $val := index $row . | default "" }}
39          <td>{{ printf "%v" $val | markdownify }}</td>
40        {{- end }}
41      </tr>
42    {{- end }}
43  </tbody>
44</table>
  • The usage: {{< table-snippet list "name,description" >}}

  • The result:

    namedescription
    Element 1A description of element 1.
    Element 2A description of element 2.

NB: I have generated HTML output, so the right syntax to use it, is {{< >}} and not {{% %}} which is markdown-aware.

By then, I improved the table-snippet shortcode with:
✔ Uses positional parameters only
✔ Looks in the folder ./page/table first, then ./data
✔ Handles nested path rss/my-super-list
✔ Optional sorting arguments
✔ Optional filtering boolean arguments

  • The usage now: {{< table-snippet DATA_FILE COLUMNS SORT FILTER >}}

  • Usage Example: {{< table-snippet rss/list "name,description,url" name active >}}

NB about Hugo:

  • Page bundle (local to the article/table or article/tables) = Scoped to the page
  • Global data directory (data/) = Reusable across the site
  • Hugo behavior is format-agnostic configuration, it matches all config files (.yaml, .yml, .json, .toml all work)

Some ideas for future shortcodes

Here are a few additional shortcode ideas that would fit well in an IT-focused blog:

  • Diff viewer — render configuration or code changes in a readable diff format

  • Badges — display states such as Active, Disabled, or Deprecated with custom styles

  • Timelines — visualize chronological steps, migrations, or project history

Summary

Hugo shortcodes act as a “controller”. It allows you to:

📄 Keeps Markdown clean and readable

🔁 Makes data reusable across multiple posts. (Loads data page or data/)

🧩 Separates content from presentation

🛠 Makes large among of data easy to maintain

⚠️ Handles errors, Fails loudly if the file is missing

Well-designed shortcodes turn repetitive documentation patterns into reusable, expressive building blocks.

By contrast, partials are used for reusable rendering logic:

  • Receive normalized data
  • Output HTML
  • Apply formatting rules
Thursday, February 5, 2026 Friday, January 16, 2026