NNicheStackTutorials
← All tutorials
Hugo

How to Add Custom Shortcodes in Hugo Without Breaking Your Theme

Override shortcodes in layouts/shortcodes/ instead of forking PaperMod. Callout, tip, and warning blocks that survive theme updates.

·12 min read·Hugo
How to Add Custom Shortcodes in Hugo Without Breaking Your Theme cover

Tested on: Hugo 0.139.4, PaperMod v8.0.0, Windows 11 + Ubuntu 22.04 CI Site: 240 posts, English only, deployed on Cloudflare Pages

Mission

I needed callout boxes (warning, tip, error) without forking PaperMod. Every theme update had been a merge nightmare after I edited files inside themes/PaperMod/.

The rule: never edit themes/

Hugo resolves shortcodes in this order: your site layouts/shortcodes/ beats the theme. Same for partials.

my-blog/
├── layouts/
│   └── shortcodes/
│       ├── callout.html
│       ├── tip.html
│       └── warning.html
├── assets/css/extended/
│   └── shortcodes.css
├── content/posts/
└── hugo.toml

Working callout shortcode

File: layouts/shortcodes/callout.html

{{ $type := .Get "type" | default "info" }}
{{ $title := .Get "title" }}
<aside class="hc-callout hc-{{ $type }}" role="note">
  {{ with $title }}<p class="hc-callout-title"><strong>{{ . }}</strong></p>{{ end }}
  <div class="hc-callout-body">{{ .Inner | markdownify }}</div>
</aside>

Use percent delimiter when inner content has markdown:

{{% callout type="warning" title="Heads up" %}}
This uses **bold** and `inline code`.
{{% /callout %}}

Angle delimiter (<<%) HTML-escapes inner markdown — I learned that the hard way.

CSS PaperMod actually bundles

Put styles in assets/css/extended/shortcodes.css — PaperMod auto-imports everything under assets/css/extended/.

.hc-callout {
  border-left: 4px solid;
  padding: 0.75rem 1rem;
  margin: 1.25rem 0;
  border-radius: 0 6px 6px 0;
  font-size: 0.95rem;
  line-height: 1.6;
}
.hc-info    { border-color: #3b82f6; background: #eff6ff; }
.hc-warning { border-color: #f59e0b; background: #fffbeb; }
.hc-error   { border-color: #ef4444; background: #fef2f2; }
.hc-tip     { border-color: #10b981; background: #ecfdf5; }
.hc-callout-title { margin: 0 0 0.35rem; }
.hc-callout-body p:last-child { margin-bottom: 0; }

Errors I actually hit

1. Shortcode not found

WARN  shortcode "callout" not found

Cause: file was named Callout.html. Hugo shortcode filenames must be lowercase.

2. Markdown not rendering

Used {{< callout >}} with **bold** inside. Fix: switch to {{% callout %}}.

3. CSS never applied

I put CSS in static/css/shortcodes.css and linked manually. PaperMod ignores that path. Use assets/css/extended/.

Tip shortcode (thin wrapper)

layouts/shortcodes/tip.html:

{{ $_hugo_config := `{ "version": 1 }` }}
{{ $title := .Get "title" | default "Tip" }}
{{ partial "shortcodes/callout.html" (dict "type" "tip" "title" $title "Inner" .Inner) }}

Actually simpler — just duplicate the aside with type="tip" hardcoded. Partial chaining broke when I renamed files.

Verify locally

hugo server -D --disableFastRender --navigateToChanged
# Open http://localhost:1313/posts/your-test-post/
# View Page Source → search "hc-callout"

Build production bundle:

hugo --gc --minify
grep -r "hc-callout" public/posts/ | head

Theme update still works

cd themes/PaperMod
git fetch && git checkout v8.0.0
cd ../..
hugo server

No conflicts — I never touched themes/.

Result

  • 3 shortcodes live across 240 posts
  • Zero theme fork
  • PaperMod v7 → v8 upgrade: 4 minutes, no shortcode regressions

Tags: HugoShortcodesPaperModTheme