text/template and html/template

Updated

September 8, 2026

text/template and html/template

Overview

The template packages render text with actions ({{...}}). Use html/template for browser HTML (context-aware escaping). Use text/template for configs, emails-as-text, code generation, and non-HTML output.

Web-focused usage: Templating and rendering.

When to use which

Package Escaping Use
html/template Yes (HTML/JS/CSS/URL contexts) Web pages, HTML emails
text/template No Nginx configs, Markdown, codegen, plain text

Serving user data as HTML with text/template is an XSS footgun.

Core loop: parse once, execute many

import "html/template"

tmpl, err := template.New("hi").Parse(`Hello, {{.Name}}!`)
if err != nil {
    log.Fatal(err)
}
err = tmpl.Execute(os.Stdout, struct{ Name string }{"Gopher"})
// Hello, Gopher!

From files:

tmpl, err := template.ParseFiles("layout.tmpl", "page.tmpl")
// or template.ParseFS(fsys, "templates/*.tmpl")

Actions you will use constantly

{{.Field}}           // print field
{{if .OK}}yes{{else}}no{{end}}
{{range .Items}}{{.}}{{end}}
{{with .User}}{{.Email}}{{end}}
{{/* comment */}}

Pipelines:

{{.Title | printf "%q" | html}}

Define and execute named templates

{{define "layout"}}
<html><body>{{template "content" .}}</body></html>
{{end}}

{{define "content"}}
<h1>{{.Title}}</h1>
{{end}}
err := tmpl.ExecuteTemplate(w, "layout", data)

FuncMap

funcs := template.FuncMap{
    "upper": strings.ToUpper,
    "cents": func(n int) string {
        return fmt.Sprintf("$%.2f", float64(n)/100)
    },
}
tmpl, err := template.New("price").Funcs(funcs).Parse(`{{cents .Price}} {{upper .Title}}`)

Register Funcs before Parse. Only expose pure, trusted helpers.

html/template safety

// Auto-escaped:
// {{.Comment}}  from user → &lt;script&gt;...

// Escape hatch (trusted HTML only):
type page struct {
    Body template.HTML
}
Type Meaning
template.HTML Trusted HTML fragment
template.JS Trusted JS
template.CSS Trusted CSS
template.URL Trusted URL

Never cast raw request input to these types.

Whitespace and formatting

{{- /* trim left */ -}}
{{.Name -}}  {{/* trim right */}}

- next to {{ / }} trims adjacent whitespace—useful for tidy HTML or generated code.

text/template for codegen

const stub = `package {{.Pkg}}

func {{.Name}}() string { return {{printf "%q" .Msg}} }
`
t := template.Must(template.New("stub").Parse(stub))
var buf bytes.Buffer
_ = t.Execute(&buf, struct {
    Pkg, Name, Msg string
}{"demo", "Hello", "hi"})
// gofmt the result in real tools

Common errors

Error Cause
complete and no parse result Empty parse / wrong name
no template "X" ExecuteTemplate name mismatch
Missing data prints <no value> Field name wrong or nil pipeline
Func not defined Funcs registered after Parse

Rules of thumb

Do Don’t
Parse at startup Parse templates on every request
html/template for HTML Build HTML with fmt + user strings
Pass view structs Pass *sql.DB into templates
template.Must only at init Must in request paths (panic under load)

Try next

  1. Parse a layout + content from embed.FS and execute both HTML and a JSON-free page.
  2. Add a FuncMap that formats time.Time as RFC3339.
  3. Intentionally inject <script> via data and confirm html/template escapes it.