Alright, let’s get our hands dirty with the three most important variables you’ll be wrestling with inside your shortcode templates: .Get, .Inner, and .Page. Think of them as the tools Hugo hands you to crack open the content your user provided and the context of the page they’re on. They’re powerful, but they have their quirks. I’ll call them out.

The .Get Method: Your Swiss Army Knife (That Sometimes Forgets a Blade)

You use .Get to fetch a parameter passed into your shortcode from the markdown file. It seems simple, and for the most part, it is. But its behavior changes based on whether you’re dealing with a positional parameter or a named parameter, and this is where people face-plant.

If your shortcode is called with unnamed parameters, like &#123;&#123;< myshortcode "param1" 42 >&#125;&#125;, .Get works by index. .Get 0 gets “param1”, .Get 1 gets 42. Indices start at zero, because this is computing, and we don’t acknowledge the number one.

If you use named parameters, like &#123;&#123;< myshortcode title="param1" count=42 >&#125;&#125;, you access them by name: .Get "title" and .Get "count".

Here’s the kicker, the bit of absurdity I must call out: .Get on a named parameter returns an empty string if the name doesn’t exist, but .Get on an out-of-bounds positional parameter returns nil. This is a fantastic way to introduce subtle bugs if you’re not careful. Always check what you’re getting.

{{/* Let's say our shortcode is called: {{< figure src="cat.jpg" alt="A cat" >}} */}}

{{ $imageSrc := .Get "src" }}
{{ $altText := .Get "alt" }}

{{/* Good practice: Check for existence, especially for required params */}}
{{ if not $imageSrc }}
    {{ errorf "The 'src' parameter is required for the 'figure' shortcode on page %s" .Page.Path }}
{{ end }}

<figure>
    <img src="{{ $imageSrc }}" alt="{{ $altText | default "An image" }}">
    <figcaption>{{ .Get "caption" }}</figcaption> {{/* This is safe; it will just be empty if not provided */}}
</figure>

The .Get method also allows for chaining with a dot to access nested data structures, but this is almost exclusively used with .Page (which we’ll get to). For instance, .Get "data" isn’t common, but .Page.Get "data" is.

.Inner: The Content Between the Tags

When you use a paired shortcode (the one with an opening and closing tag, like &#123;&#123;% myshortcode %&#125;&#125;This is the inner content&#123;&#123;% /myshortcode %&#125;&#125;), the magic .Inner variable contains all that delicious content.

The first thing you must know: .Inner is already a string containing the rendered Markdown. Hugo has already processed it. This is brilliant because it means you can wrap content in a custom HTML structure without worrying about parsing Markdown yourself. But it’s also a common pitfall: if you try to pass parameters into the .Inner content, it won’t work. The inner content is just text; it’s not part of the shortcode’s parameter logic.

{{/* shortcode: callout.html */}}
<div class="callout callout-{{ .Get "type" | default "note" }}">
    <h4>{{ .Get "title" | default "Note" }}</h4>
    <div class="callout-inner">
        {{ .Inner }} {{/* This will output: <p>This is a <strong>very important</strong> note!</p> */}}
    </div>
</div>

Used in Markdown:

{{% callout type="warning" title="Heads Up" %}}
This is a **very important** note!
{{% /callout %}}

A crucial best practice: if you’re not using a paired shortcode, do not reference .Inner. It will be empty, and you’ll just be shouting into the void.

.Page: Your Gateway to Everything Else

This is the big one. While .Get and .Inner are about the what (the content of the shortcode itself), .Page is about the where. It gives your shortcode the context of the page it’s being used on. It’s your backstage pass to the entire page’s front matter and site variables.

Want to get the page’s title? .Page.Title. Want to see its tags? .Page.Params.tags. Want to know its permalink? .Page.Permalink. This is how you make dynamic shortcodes that aren’t just static widgets.

{{/* shortcode: pageinfo.html */}}
<aside class="page-metadata">
    <p>This shortcode is living on a page titled <strong>{{ .Page.Title }}</strong>.</p>
    {{ with .Page.Params.author }}<p>It was written by the illustrious {{ . }}.</p>{{ end }}
    <p>The site it belongs to is called <strong>{{ .Page.Site.Title }}</strong>.</p>
</aside>

The most powerful, and often misused, feature is accessing page-specific data files via .Page.Get. Say you have data/authors/quincy.yaml. You can grab that data from within a shortcode for the current page.

{{ $authorName := .Get "name" | default "quincy" }}
{{ $authorData := .Page.Get (printf "data/authors/%s" $authorName) }}

{{ if $authorData }}
    <div class="author-bio">
        <img src="{{ $authorData.avatar }}" alt="{{ $authorData.name }}">
        <h3>{{ $authorData.name }}</h3>
        <p>{{ $authorData.bio }}</p>
    </div>
{{ else }}
    {{ errorf "Author data for '%s' not found in data/authors/" $authorName }}
{{ end }}

The pitfall here? Performance. Accessing .Page and its methods is cheap, but if you start doing complex operations or querying huge data files inside a shortcode used on hundreds of pages, you’ll feel it at build time. Use this power judiciously. It’s there to solve real problems, not to show off.