Right, let’s talk about the two things you’ll be throwing around more than confetti at a Go template party: the dollar sign $ and the dot .. At first glance, they seem like simple, almost trivial concepts. And then you try to use them, and you feel like you’re trying to explain quantum mechanics to a golden retriever. Don’t worry, we’ve all been there. It’s not you; it’s the syntax. Let’s demystify it.

The Almighty Dot (.)

Think of the dot . as your current context. It’s the “this” of the template world. It’s a cursor that points to whatever data you’re supposed to be working with right now. When you start a template, Hugo hands you a giant data structure (a page, some site-wide data, etc.) and says, “Here, this is everything. The dot is pointing at it.”

For example, if you’re in the context of a single page, . represents that page. To get its title, you use the .Title field because, from the dot’s perspective, Title is a property right there on the object it’s currently pointing to.

<h1>{{ .Title }}</h1>
<p>This content lives at {{ .Permalink }}</p>

But context changes. When you range over a collection—say, the pages in a section—the dot inside the range block shifts. It’s no longer pointing at the original page data; it’s now pointing at each individual element in the collection you’re looping through.

<ul>
{{ range .Pages }} <!-- . here is the original context (e.g., a section page) -->
    <li>{{ .Title }}</li> <!-- . HERE is the current page in the loop! -->
{{ end }}
</ul>

This is the single most common “gotcha.” You start a range, forget the dot has changed, and then try to access .Site.Title from inside it. You’ll get nothing but a blank stare from the template parser because inside the loop, . is just a regular page, and a regular page doesn’t have a .Site property. It’s like asking a carrot about agricultural export tariffs—it’s just not in its wheelhouse.

The Rooted Dollar Sign ($)

This is where $ comes to the rescue. $ is your anchor. It always points to the initial context passed into the template, the big starting data structure. It’s your way of saying, “No, I don’t mean the current thing in the loop; I mean the original thing.”

So, to fix our previous problem and access site-wide data from inside a range loop:

<ul>
{{ range .Pages }}
    <li>{{ .Title }} - Published on {{ .Date.Format "Jan 2, 2006" }} for the site "{{ $.Site.Title }}"</li>
{{ end }}
</ul>

See that? $.Site.Title. We use the $ to break out of the current, narrowed-down context (.) and reach back to the root scope to find the .Site variable. It’s your get-out-of-jail-free card for scope issues.

When to Use Which (And Why It Matters)

The rule is simple, yet brutal if you forget: Always use the most specific context possible, and use $ to escape when you need to.

Sticking with the simple dot . unless you have to not only makes your code more portable (it will work correctly in different scopes) but also more performant. The template engine has to do less work resolving where a variable is. Use $ explicitly when you need to reference something from the root scope from within a narrower scope, like a with or range block.

Let’s look at a more complex, real-world pitfall. Imagine you have a partial template for rendering a “related articles” widget. You pass it a single article object.

{{ partial "related-articles" . }}

Inside related-articles.html:

<div class="widget">
  <h3>Read This Next</h3>
  {{ with .GetTerms "tags" }}
    <p>Articles tagged with:
    {{ range . }}
      <a href="{{ .Permalink }}">{{ .LinkTitle }}</a>
    {{ end }}
    </p>
    <!-- Now try to get the current page's title... Oops! -->
    <p>Wait, what was the article called again? {{ .Title }}?</p>
  {{ end }}
</div>

Boom. You’re stuck. The with block changed the context to the first tag’s page. Inside it, .Title is the tag’s title, not the original article’s. This code would be hilariously wrong. The fix? Anchor yourself with $.

<div class="widget">
  <h3>Read This Next</h3>
  {{ with .GetTerms "tags" }}
    <p>Articles tagged with:
    {{ range . }}
      <a href="{{ .Permalink }}">{{ .LinkTitle }}</a>
    {{ end }}
    </p>
    <p>Ah, right: the article was "{{ $.Title }}".</p>
  {{ end }}
</div>

There. Perfect. The $ lets us reach outside the with block’s context and back to the original article we passed into the partial. It’s the difference between confusion and clarity. Master the dance between . and $, and you’ve just conquered about 80% of the frustration people have with Go templates. The other 20% involves scratch and dict, but that’s a story for another section.