Use html/template, not text/template, whenever the output is HTML. Both packages share one API, but only html/template escapes data based on where it lands in the page. Its security model treats the template as trusted and the data passed to Execute as untrusted.
Same API, different safety
The Go documentation says html/template "should be used instead of text/template whenever the output is HTML." If you feed a string containing a script tag to text/template, it writes it out raw. html/template writes escaped text instead.
Because the two packages look the same, swapping the import is an easy mistake and a quiet one. Code reviews should flag any text/template import in a package that serves web pages.
Contextual autoescaping
Escaping rules differ between an HTML body, an attribute, a script block, a style block and a URL. html/template parses the template and adds the right escaping function at each action. The docs say actions can appear within JavaScript, CSS and URI contexts, and the escaping is contextual.
You do not call an escape function yourself. That removes the most common source of XSS in server rendered code: someone forgot to escape one field.
The trust model
Per the docs: "The security model used by this package assumes that template authors are trusted, while Execute's data parameter is not."
Two consequences:
- Do not let users upload or edit template text and then parse it. That hands them the trusted side of the model.
- Do put user supplied values in the data, never into the template string.
t := template.Must(template.New("page").Parse(
`<p>Hello, {{.Name}}</p>`))
t.Execute(w, map[string]string{"Name": userInput})
Typed strings turn escaping off
The package has types such as template.HTML, template.JS and template.URL that mark a value as already safe. The docs warn that the content should come from a trusted source because it is included verbatim in the output.
So these are the lines to search for in review:
template.HTML(wrapped around anything that came from a request, a database field a user edits, or an upstream API.template.JS(andtemplate.URL(with dynamic values.
If you must render user HTML, run it through a dedicated sanitizer first and document why. The docs say HTML should not be used for third party HTML or HTML with unclosed tags or comments.
The ZgotmplZ marker
If an unsafe value ends up in a URL or CSS context, the package replaces it at runtime with the string ZgotmplZ. Seeing that in a page is a sign the filter worked. Do not "fix" it by wrapping the value in a typed string. Check why the value was unsafe.
Review checklist
- No
text/templatefor HTML output. - No user controlled template text.
- Every
template.HTML,template.JSortemplate.URLconversion has a trusted source. - A Content Security Policy as a second layer.
Safeguard's dependency scanning helps you keep the web frameworks and sanitizer libraries around your templates patched. A free scan is at gold.safeguard.sh/scan.
Quick check
Is html/template output always XSS free?
No. Typed strings bypass escaping, and user controlled template text breaks the trust model.
Can I use text/template for JSON responses?
It is not meant for that either. Use encoding/json for JSON and html/template for HTML.
Sources
Last reviewed: 19 October 2026.