{} The Go Reference

Essentials · Stdlib · Intermediate

text/template & html/template

Data-driven text generation — actions, fields and pipelines, if/range/with control flow, custom funcs, and why html/template's context-aware auto-escaping is the one you must use for the web.

Essentials Intermediate ⏱ 6 min read Complete

📝 Analogy

A template is a fill-in-the-blanks form: you write the fixed text once, mark the blanks with {{…}} actions, then hand the engine a data value to fill them. The text/template engine fills any text; its twin html/template fills HTML and checks the ink — escaping anything that could turn data into executable markup. For the web you always want the careful twin.

text/template basics

You parse a template string into a *template.Template, then execute it against a data value, writing the result to any io.Writer. Actions in {{…}} pull from the current data context, written as the dot (.). {{.Field}} reads an exported struct field, a map key, or a zero-argument method:

template.go — editable & runnable
package main

import (
"os"
"text/template"
)

type Invoice struct {
Customer string
Total    float64
}

func main() {
// Must panics on a parse error — good for templates known at startup.
t := template.Must(template.New("inv").Parse(
	"Dear {{.Customer}}, your total is {{printf \"$%.2f\" .Total}}.\n",
))

t.Execute(os.Stdout, Invoice{Customer: "Ada", Total: 123.456})
// Dear Ada, your total is $123.46.
}

Control flow: if, range, with

Templates have just enough logic to shape output: {{if}}/{{else}} for conditionals, {{range}} to iterate (re-binding the dot to each element), and {{with}} to narrow the dot to a sub-value. There are no arbitrary expressions — that’s deliberate; logic belongs in Go, presentation in the template:

control.go — editable & runnable
package main

import (
"os"
"text/template"
)

type Cart struct {
User  string
Items []string
}

func main() {
const page = "{{.User}}'s cart:\n" +
	"{{range .Items}}  - {{.}}\n{{else}}  (empty)\n{{end}}" +
	"{{if .Items}}Total items: {{len .Items}}\n{{end}}"

t := template.Must(template.New("cart").Parse(page))

t.Execute(os.Stdout, Cart{User: "Bob", Items: []string{"pen", "ink"}})
t.Execute(os.Stdout, Cart{User: "Cleo"}) // empty cart hits {{else}}
}

Functions and pipelines

A template can call functions. Built-ins include len, printf, index, and comparisons (eq, lt, …). You add your own via a template.FuncMap — attached with .Funcs(...) before parsing, because the parser must recognize the names. The pipeline operator | feeds one result into the next, just like a shell:

funcs.go — editable & runnable
package main

import (
"os"
"strings"
"text/template"
)

func main() {
funcs := template.FuncMap{
	"upper": strings.ToUpper,
	"repeat": strings.Repeat,
}

t := template.Must(
	template.New("f").Funcs(funcs).Parse(
		"{{.Name | upper}} {{repeat \"!\" 3}}\n",
	),
)
t.Execute(os.Stdout, map[string]string{"Name": "go"})
// GO !!!
}

html/template: auto-escaping for safety

Switching the import to html/template keeps the identical API but adds the feature that matters for the web: context-aware auto-escaping. The engine tracks whether a value is landing in HTML text, an attribute, a URL, or a <script> block, and escapes it the right way for that spot. This is what stops user input like <script>steal()</script> from becoming executable — the cross-site scripting (XSS) defense you get for free:

html.go — editable & runnable
package main

import (
"html/template"
"os"
)

func main() {
t := template.Must(template.New("p").Parse(
	"<p>Hello, {{.}}</p>\n",
))

// Hostile input is neutralized automatically.
t.Execute(os.Stdout, "<script>steal()</script>")
// <p>Hello, &lt;script&gt;steal()&lt;/script&gt;</p>

t.Execute(os.Stdout, "Ada & Bob")
// <p>Hello, Ada &amp; Bob</p>
}

The same data through text/template would emit the raw <script> tag verbatim — a live XSS hole. That difference is the whole reason both packages exist.

graph LR
D["data value"] --> T{"which package?"}
T -->|text/template| RAW["bytes emitted verbatim<br/>(reports, configs, code-gen)"]
T -->|html/template| ESC["context-aware escaping<br/>(HTML · attr · JS · URL)"]
ESC --> SAFE["safe HTML — XSS neutralized"]

text/template vs html/template

text/templatehtml/template
APIidenticalidentical (drop-in)
Escapingnone — verbatimautomatic, context-aware
Use foremails, configs, code generation, CLI outputany HTML served to a browser
XSS safetyyour responsibilityhandled by the engine
Underlyingtext/templatewraps text/template + escaper

Under the hood: parse once, execute many

Parsing compiles the template text into an internal tree; executing walks that tree against your data. Parsing is the expensive part, so do it once — at package init or server startup — and execute per request. A *template.Template is safe for concurrent Execute calls once parsed, so one parsed template serves all your goroutines. Real apps usually template.ParseGlob("templates/*.html") to load a whole directory into one template set, then ExecuteTemplate(w, "page.html", data) to render a named one — letting templates {{define}} and {{template}} include each other for layouts.

⚠️ Use html/template for the web, and parse before serving

Two rules. (1) For anything a browser renders, use html/templatetext/template does zero escaping and turns user data into an injection vector. Don’t defeat the escaper by marking untrusted data template.HTML. (2) Parse templates once at startup with template.Must, not on every request — re-parsing per request is slow and a missing-file error would surface mid-request instead of at boot. Also: only exported struct fields are visible to templates (same reflection rule as JSON).

See also

  • encoding/json — the other way to turn Go data into a wire format; both rely on exported fields.
  • http-server — render html/template output straight to the http.ResponseWriter.
  • strings & bytesstrings.Builder is a handy Execute target when you want a string.
  • fmt & ioExecute writes to any io.Writer.

Next: working with dates, durations and timers — time.

Check your understanding

Score: 0 / 5

1. What is the single most important reason to use html/template instead of text/template for web pages?

html/template understands where in the document a value lands and escapes it appropriately, neutralizing injected <script> and similar. text/template emits bytes verbatim — using it for HTML is an XSS hole.

2. In a template, what does {{.Name}} mean?

The dot (.) is the current data context. {{.Name}} accesses the Name exported field of a struct, the "Name" key of a map, or a zero-arg method Name. Inside {{range}} or {{with}}, the dot is rebound to the element.

3. Why is template.Must(template.New(...).Parse(...)) a common idiom?

Templates defined as constants in source either parse or they don't — there's no runtime recovery. template.Must wraps the (\*Template, error) pair and panics on error, so a typo surfaces at program start, not on first request.

4. What does {{range .Items}} … {{end}} do?

range iterates a slice, array, map, or channel. Inside the block, . becomes the current element (use {{.}} or {{.Field}}). An optional {{else}} runs when the collection is empty.

5. How do you add a custom function (e.g. uppercase) usable inside a template?

template.FuncMap maps names to Go functions; attach it with .Funcs(...) BEFORE Parse (the parser must know the names). Then invoke as {{upper .Name}} or via a pipeline {{.Name | upper}}.

Comments

Sign in with GitHub to join the discussion.