skip to content

How does html/template choose an escaper for {{.Name}} from where it appears in the page?

level: middleimportance: must knowfreq 52%

answer

  1. it reads the markup, not the value
  2. a state is tracked at every action
  3. attribute, script and href differ
  4. unsafe URLs become a searchable marker
  5. the decision is static, made once

basics

~20 s

html/template parses the markup around each action, tracks the output state there, and rewrites the tree to call a matching escaper: entities in text, a JavaScript literal in a script block, percent-encoding plus a scheme filter in a URL.

solid answer

~40 s

The package does not look at the value; it looks at the template. During its escaping pass it parses the markup you wrote, and at each action it knows the output state — HTML text, an attribute name, a quoted or unquoted attribute value, inside `<script>`, inside `<style>`, inside a URL, inside a URL's query. It then rewrites that action's pipeline to call the escaper for that state. So `{{.Name}}` in body text becomes entities (`<` to `&lt;`, `"` to `&#34;`); the same action inside `<script>var u = {{.Name}};</script>` is emitted as a JavaScript literal, already quoted, with `<` written as `\u003c`; and inside `href="{{.U}}"` it is percent-encoded and run through a scheme filter, so `javascript:alert(1)` renders as `#ZgotmplZ`. That is also why you never wrap an action in your own quotes.

code

go · 4 lines
go
const page = `<a href="{{.Home}}" title="{{.Name}}">{{.Name}}</a>
<script>var user = {{.Name}};</script>`

tmpl := template.Must(template.New("page").Parse(page))

go deeper

for a junior

Know that the same value is escaped differently depending on where the action sits, and that you never add your own quotes or hand-escaping around it.

for a middle

Explain the escaping pass: html/template parses the surrounding markup, tracks the output state at each action, and rewrites the tree to call the matching escaper for text, attributes, script, style and URLs.

for a senior

Read rendered output as evidence. Recognise ", a quoted JavaScript literal and #ZgotmplZ on sight, and say which escaper produced each when a page renders wrong.

for a principal

Decide how much markup your templates may assemble at all. Dynamic tags and attributes defeat static context analysis, so a house style that keeps templates analysable is what makes the guarantee reviewable.

## The escaping pass, not the value A useful thing to internalise about `html/template` is that it never inspects the runtime value to decide how to escape. It inspects the *template*. Before a template is executed, the package runs an escaping pass: it walks the parse tree produced by the parser, and as it walks it feeds the literal text through a small HTML parser, keeping a context — roughly, which state the output is in and, where relevant, which element and attribute it is inside. When the walk reaches an action such as `{{.Name}}`, the context at that point is known, and the pass rewrites the action's pipeline so the value is passed through the escaping function that matches that state. Execution then runs the rewritten tree. The escaping decision is therefore static, made once from the markup you wrote, and identical for every value that flows through that action. ## The positions and what comes out **HTML text.** `<p>{{.Name}}</p>` gets entity escaping. `<` becomes `&lt;`, `>` becomes `&gt;`, `&` becomes `&amp;`, and the quote characters become numeric character references — `"` renders as `&#34;` and `'` as `&#39;`, which is why Go-rendered HTML looks slightly different from output produced by hand-written escapers that emit `&quot;`. **A quoted attribute value.** `<a title="{{.Name}}">` gets escaping that cannot terminate the attribute. An *unquoted* attribute value is a different state and needs more characters escaped, because whitespace or a backtick would end the value; the pass knows which one you wrote. **A script block.** Inside `<script>`, the value is not HTML at all — it is emitted as a JavaScript literal. A string arrives already quoted, with characters that could break out of the script written as escapes (`<` as `\u003c`). This is the reason writing `var u = "{{.Name}}";` and `var u = {{.Name}};` are different templates: in the second the package supplies the quotes. **A URL.** `href`, `src` and friends are URL positions. The value is percent-encoded, and — this is the part people meet by accident — it is passed through a filter on the scheme. A URL whose scheme is not one of the ordinary safe ones is not emitted; the output becomes `#ZgotmplZ`, a deliberately searchable marker. `href="/search?q={{.Q}}"` is a further sub-state: after the `?` the value is escaped as a query component rather than as a whole URL. **A style block or style attribute.** CSS is its own state with its own filtering, and a value that cannot be rendered safely there also yields the `ZgotmplZ` marker. ## What the static decision cannot do Because the decision comes from the markup, the markup has to be analysable. A template that builds its own tags or attribute names from data — `<{{.Tag}}>` or `<a {{.Attrs}}>` — has no single output position the pass can compute, and the pass fails rather than guessing. Likewise, a value that has to be usable in two positions is escaped for each of them separately: put the same field in a `title` attribute and in a script block on the same page and you get two different byte sequences from one string. That is correct, and it is a common source of "why does my page show `&#34;`" confusion when someone inspects rendered source. ## Reading the output as evidence Three byte patterns tell you which escaper ran, which is the fastest way to debug a rendering problem. `&#34;` and `&lt;` mean an HTML text or attribute escaper ran. `\u003c` inside a script block means the JavaScript literal escaper ran. `#ZgotmplZ` in an `href` means the URL scheme filter rejected the value — the template is fine, the data was not acceptable in that position. None of these are errors returned to your code; they are the escapers doing their job in the output stream. ## The practical rules that fall out Write the markup the way you want it to render and let the actions sit in it plainly. Do not add quotes around an action inside a script block. Do not hand-escape in a pipeline. Do not assemble a URL by string concatenation in Go and hand it in as a whole — build the parts and let the template escape them where they land. Every one of those habits fights an engine that already knows more about the position than the calling code does.

  • Why does a template like `<a {{.Attrs}}>` fail instead of escaping something?
    Because the escaping pass computes the output position statically from the markup, and an action that supplies attribute names has no single position — the bytes could become an attribute name, a value, or the start of an event handler. The package refuses to guess and reports the failure rather than emitting something it cannot reason about.
  • The same field renders as different bytes in an attribute and in a script block. Is that a bug?
    No, it is the design. Escaping is per position, so one string legitimately produces `&#34;` in markup and a quoted JavaScript literal in a script. If you need the identical bytes in both places, that is a signal to pass the data to the page once — for example as a JSON payload in one script action — rather than to defeat the escaper.
  • Should you apply the predefined html or urlquery escaper yourself in an html/template pipeline?
    No. The package already escapes for the position, so a manual escaper is at best redundant and at worst changes what the contextual escaper sees; html/template reports an error for pipelines where a predefined escaper conflicts with what it would do. Leave the action plain.

saying these in an interview costs you the question

  • Adds their own quotes around an action inside a script block
  • Thinks one escaper is applied everywhere on the page
  • Believes the escaper inspects the value to decide
  • Reads #ZgotmplZ as a parser bug rather than a filter result
  • Builds attribute names from template data