# How Hugo's Shortcode Rendering Pipeline Works: A Deep Dive into the Internals

> Explore Hugo's shortcode rendering pipeline internals. Discover how it parses content, handles inner templates, and optimizes rendering for faster site builds. Learn the deep dive.

- Repository: [GoHugo.io/hugo](https://github.com/gohugoio/hugo)
- Tags: internals
- Published: 2026-02-28

---

**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`](https://github.com/gohugoio/hugo/blob/main/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`](https://github.com/gohugoio/hugo/blob/main/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`](https://github.com/gohugoio/hugo/blob/main/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`](https://github.com/gohugoio/hugo/blob/main/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`](https://github.com/gohugoio/hugo/blob/main/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`](https://github.com/gohugoio/hugo/blob/main/hugolib/page__content.go) (lines 993-1020) iterates over `itemsStep2`. For each shortcode:

1. It checks whether to insert a placeholder or render immediately
2. If rendering immediately, it calls the `shortcodeRenderFunc` returned by `prepareShortcode`
3. The function executes `doRenderShortcode`, which loads the `TemplInfo`, executes the template with `ShortcodeWithPage` context, 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`](https://github.com/gohugoio/hugo/blob/main/hugolib/page__content.go) | Contains `RenderShortcodes` (lines 993-1020), the core loop that processes shortcode items and manages placeholder substitution |
| [`hugolib/shortcode.go`](https://github.com/gohugoio/hugo/blob/main/hugolib/shortcode.go) | Defines the `shortcode` struct, `prepareShortcode`, and `doRenderShortcode`; handles placeholder generation (`HAHAHUGOSHORTCODE…HBHB`) and template execution |
| [`tpl/tplimpl/templatetransform.go`](https://github.com/gohugoio/hugo/blob/main/tpl/tplimpl/templatetransform.go) | Implements `collectInnerInShortcode` (lines 20-44) to detect `.Inner` usage in templates and set `ParseInfo.IsInner` |
| [`tpl/tplimpl/templatestore.go`](https://github.com/gohugoio/hugo/blob/main/tpl/tplimpl/templatestore.go) | Stores and retrieves compiled shortcode templates via the TemplateStore |
| [`parser/pageparser/pageparser.go`](https://github.com/gohugoio/hugo/blob/main/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:

```go
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`](https://github.com/gohugoio/hugo/blob/main/layouts/shortcodes/hello.html):

```go
{{ $_hugo_config := `{ "version": 1 }` }}
<h1>Hello {{ .Get 0 }}</h1>

```

When Hugo processes this template:
1. `collectInnerInShortcode` detects no `.Inner` usage, leaving `ParseInfo.IsInner` as false
2. During rendering, `doRenderShortcode` executes the template with `ShortcodeWithPage` context
3. If the shortcode uses `{{% %}}` delimiters and `doMarkup` is true, `tpl.Context.IsInGoldmark` enables 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 `*shortcode` struct in [`hugolib/shortcode.go`](https://github.com/gohugoio/hugo/blob/main/hugolib/shortcode.go) serves as the lightweight representation of shortcode calls, tracking parameters, inner content, and position data
- `collectInnerInShortcode` in [`tpl/tplimpl/templatetransform.go`](https://github.com/gohugoio/hugo/blob/main/tpl/tplimpl/templatetransform.go) analyzes templates during compilation to detect `.Inner` usage, setting `ParseInfo.IsInner` to control content flow
- Placeholder substitution (using strings like `HAHAHUGOSHORTCODE…