Go by Example

Text Templates

text/template and html/template - template actions, custom functions, parsing once at startup, and avoiding XSS with html/template.

Go's template packages (text/template and html/template) render dynamic content by combining a template string with data. html/template automatically escapes HTML and should always be used for browser output. Both packages share the same API.

Parse and execute a basic template. Template actions use {{ and }} delimiters. . refers to the data passed to Execute.

package main
 
import (
    "os"
    "text/template"
)
 
func main() {
    tmpl := template.Must(template.New("greeting").Parse(
        "Hello, {{.Name}}! You have {{.Count}} messages.\n",
    ))
 
    data := struct {
        Name  string
        Count int
    }{"Alice", 3}
 
    tmpl.Execute(os.Stdout, data)
    // Hello, Alice! You have 3 messages.
}

Use {{if}}, {{else}}, {{range}}, and {{with}} to control output. {{range}} iterates over slices, maps, and channels.

package main
 
import (
    "os"
    "text/template"
)
 
const tmplText = `
{{- range .Items}}
  - {{.}}
{{- end}}
{{if .HasMore}}... and more{{end}}
`
 
func main() {
    tmpl := template.Must(template.New("list").Parse(tmplText))
 
    data := struct {
        Items   []string
        HasMore bool
    }{
        Items:   []string{"Go", "Rust", "Python"},
        HasMore: true,
    }
 
    tmpl.Execute(os.Stdout, data)
}

Add custom functions with template.FuncMap before parsing. Functions must be registered before the template is parsed.

package main
 
import (
    "os"
    "strings"
    "text/template"
)
 
func main() {
    funcMap := template.FuncMap{
        "upper": strings.ToUpper,
        "greet": func(name string) string {
            return "Hello, " + name + "!"
        },
    }
 
    tmpl := template.Must(
        template.New("fn").Funcs(funcMap).Parse(`{{upper .Name}} - {{greet .Name}}`),
    )
 
    tmpl.Execute(os.Stdout, map[string]string{"Name": "world"})
    // WORLD - Hello, world!
}

Use html/template (same API) when generating HTML. It escapes values automatically to prevent XSS.

package main
 
import (
    "html/template"
    "os"
)
 
func main() {
    tmpl := template.Must(template.New("page").Parse(
        `<p>Welcome, {{.Username}}!</p>`,
    ))
 
    // If Username contains HTML, it is escaped automatically.
    data := struct{ Username string }{`<script>alert(1)</script>`}
    tmpl.Execute(os.Stdout, data)
    // <p>Welcome, &lt;script&gt;alert(1)&lt;/script&gt;!</p>
}

In production

Always use html/template (not text/template) for any output that reaches a browser - it contextually escapes HTML, CSS, JavaScript, and URL attributes and prevents XSS. text/template is for non-HTML output: config files, code generation, email plain text, Markdown. Parse templates once at startup (var tmpl = template.Must(template.ParseFiles(...))) rather than per request - parsing is expensive and the compiled template is safe for concurrent use. template.Must panics on parse errors, which is correct for startup-time template loading.

Enjoyed this? Get more essays on software craft delivered to your inbox.

Subscribe free