How Hugo's Shortcode Rendering Pipeline Works: A Deep Dive into the Internals
Hugo's shortcode rendering pipeline parses raw content to detect shortcode calls, analyzes templates for inner content usage, and renders them either immediately or via placeholders that get replaced after Markdown processing completes.
The gohugoio/hugo repository implements a sophisticated multi-phase pipeline for handling shortcodes. When Hugo builds a page, it processes shortcode calls through distinct stages of parsing, template analysis, and deferred rendering. Understanding this Hugo shortcode rendering pipeline is essential for developers building complex shortcodes or debugging rendering issues.
The Three Phases of the Shortcode Rendering Pipeline
Phase 1: Parsing and Shortcode Detection
The pipeline begins in hugolib/page__content.go where the page parser tokenizes the raw source. When the parser encounters {{< … >}} or {{% … %}} syntax, it creates a *shortcode struct for each call.
This struct captures:
- Name and parameters parsed from the shortcode call
- Position data (byte offsets) used later for placeholder substitution
- Inner content (raw text or nested shortcodes) for block-level shortcodes
- Inline flag indicating whether the shortcode appears inline with text
The parser stores these *shortcode instances in content.pi.itemsStep2, making them available for the rendering phase.
Phase 2: Template Analysis and Inner Content Flagging
Before rendering begins, Hugo analyzes shortcode templates to detect inner content usage. In tpl/tplimpl/templatetransform.go, the function collectInnerInShortcode inspects the template AST during compilation.
If the template accesses .Inner or .InnerDeindent, the function sets ParseInfo.IsInner = true. This flag is critical because it instructs the renderer to:
- Capture the rendered inner markup
- Pass it back to the shortcode template during execution
- Handle the content appropriately based on whether the shortcode uses
%(Markdown) or<(HTML) delimiters
Phase 3: Rendering and Placeholder Handling
The final phase occurs in hugolib/shortcode.go through prepareShortcode, which constructs a shortcodeRenderFunc closure. This function determines whether to render immediately or defer via placeholders.
Immediate rendering occurs when doRenderShortcode executes the template with a ShortcodeWithPage context, returning rendered bytes and a "more" flag indicating if additional processing is needed.
Deferred rendering uses placeholder substitution. When insertPlaceholder returns true, Hugo generates a unique placeholder string (HAHAHUGOSHORTCODE…HBHB) and writes it into the output buffer instead of the rendered content. After Goldmark finishes processing the Markdown, Hugo scans for these placeholders and replaces them with the final rendered shortcode HTML.
If the page uses Markdown and the shortcode sets doMarkup, the context flag tpl.Context.IsInGoldmark enables Goldmark to re-parse the rendered result.
Step-by-Step Walkthrough of the Rendering Process
Content Parsing Details
The pageparser (indirectly referenced through parser/pageparser/pageparser.go) tokenizes the source file and populates content.pi.itemsStep2 with *shortcode items. Each item tracks its byte position to enable precise placeholder replacement later.
Template Preparation Details
In tpl/tplimpl/templatestore.go, Hugo loads shortcode templates into the TemplateStore. During this process, collectInnerInShortcode analyzes whether templates use .Inner, setting ParseInfo.IsInner accordingly.
Render Loop Execution
The RenderShortcodes method in hugolib/page__content.go (lines 993-1020) iterates over itemsStep2. For each shortcode:
- It checks whether to insert a placeholder or render immediately
- If rendering immediately, it calls the
shortcodeRenderFuncreturned byprepareShortcode - The function executes
doRenderShortcode, which loads theTemplInfo, executes the template withShortcodeWithPagecontext, and returns rendered bytes
Placeholder Replacement Mechanics
After Goldmark processes the Markdown content, Hugo scans for the placeholder pattern HAHAHUGOSHORTCODE…HBHB. It looks up the corresponding rendered shortcode in contentPlaceholders and substitutes the placeholder with the final HTML. This enables shortcodes to output raw HTML that should not be escaped by the Markdown processor.
Final Output Generation
The fully-rendered byte slice is wrapped as template.HTML or, when Goldmark is still active, wrapped with hugocontext.Wrap to preserve the current page stack context.
Key Source Files and Their Roles
| File | Purpose |
|---|---|
hugolib/page__content.go |
Contains RenderShortcodes (lines 993-1020), the core loop that processes shortcode items and manages placeholder substitution |
hugolib/shortcode.go |
Defines the shortcode struct, prepareShortcode, and doRenderShortcode; handles placeholder generation (HAHAHUGOSHORTCODE…HBHB) and template execution |
tpl/tplimpl/templatetransform.go |
Implements collectInnerInShortcode (lines 20-44) to detect .Inner usage in templates and set ParseInfo.IsInner |
tpl/tplimpl/templatestore.go |
Stores and retrieves compiled shortcode templates via the TemplateStore |
parser/pageparser/pageparser.go |
Tokenizes source content and creates the initial *shortcode items that feed the pipeline |
Practical Code Examples
Rendering Shortcodes Programmatically
To render a page's shortcodes from Go code:
page := site.GetPage("mysection/mypage.md")
html, err := page.RenderShortcodes(context.Background())
if err != nil {
log.Fatalf("render failed: %s", err)
}
fmt.Println(html) // fully-rendered content with all shortcodes resolved
Creating an Inline Shortcode Template
Create layouts/shortcodes/hello.html:
{{ $_hugo_config := `{ "version": 1 }` }}
<h1>Hello {{ .Get 0 }}</h1>
When Hugo processes this template:
collectInnerInShortcodedetects no.Innerusage, leavingParseInfo.IsInneras false- During rendering,
doRenderShortcodeexecutes the template withShortcodeWithPagecontext - If the shortcode uses
{{% %}}delimiters anddoMarkupis true,tpl.Context.IsInGoldmarkenables Goldmark to process the rendered<h1>tag
Summary
- Hugo's shortcode rendering pipeline operates in three distinct phases: parsing and detection, template analysis, and rendering with placeholder handling
- The
*shortcodestruct inhugolib/shortcode.goserves as the lightweight representation of shortcode calls, tracking parameters, inner content, and position data collectInnerInShortcodeintpl/tplimpl/templatetransform.goanalyzes templates during compilation to detect.Innerusage, settingParseInfo.IsInnerto control content flow- Placeholder substitution (using strings like `HAHAHUGOSHORTCODE…
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →