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.
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