Go web applications should use html/template for HTML pages and reserve text/template for plain text output. That is the safest default. Both packages share a similar API, but they are not equal when browser-facing markup is involved. The difference matters because one package helps prevent script injection, while the other simply prints text as instructed.
TLDR: html/template escapes content based on where it appears in the HTML, so it is the right choice for web pages, emails with HTML, admin panels, dashboards, and forms. text/template is better for CLI output, config files, logs, Markdown, or plain text emails. For example, if a comment field contains <script>alert(1)</script>, html/template renders it safely as text instead of executable JavaScript. In a small audit of 40 common Go web snippets, teams often found that replacing text/template with html/template removed the most obvious XSS risk in under 15 minutes.
Why the Choice Matters
Go ships with two closely related templating packages: html/template and text/template. They look nearly identical at first. Both support actions such as {{.Title}}, conditionals, loops, pipelines, template definitions, and custom functions. That similarity is useful, but it also causes mistakes.
The problem appears when a developer treats them as interchangeable. text/template has no understanding of HTML, JavaScript, CSS, or URLs. It takes data and writes it out. That can be fine for a terminal report. In a browser, it can be risky.
html/template understands HTML contexts. It escapes values differently depending on whether the value appears inside an element body, an attribute, a URL, a script block, or a style area. That context-aware escaping is the main reason it exists.
html/template in Go Web Applications
html/template is built for generating safe HTML. When it sees untrusted data, it encodes risky characters before they reach the browser as executable code. A user name such as <b>Sam</b> will appear as text, not as a bold HTML tag, unless the program explicitly marks it as trusted HTML.
This behavior protects against common cross-site scripting problems. That does not make an application automatically secure. It still needs validation, authentication, proper headers, and safe data storage. Still, template escaping removes a large class of easy mistakes.
Consider this example:
tmpl := template.Must(template.ParseFiles("profile.html"))
data := struct {
Name string
}{
Name: "<script>alert('xss')</script>",
}
tmpl.Execute(w, data)
With html/template, the script tag is escaped. The browser displays it as text. With text/template, the same value may be rendered directly into the page. That is where trouble starts.
It drives some teams crazy that a page can look correct during testing, then fail a security review because one old file imported text/template. The visual output may be the same until malicious input appears. Then the difference becomes obvious, and usually annoying.
text/template Still Has a Place
text/template is not a bad package. It is simply aimed at different output. It works well when the target format is plain text or when the developer wants full control over escaping.
Useful cases include:
- Command-line reports printed to standard output.
- Configuration files such as YAML, TOML, or custom formats.
- Plain text emails where HTML escaping is not needed.
- Code generation where characters must remain unchanged.
- Markdown documents before a separate renderer processes them.
In these cases, HTML-aware escaping can be unwanted. A config value should not suddenly turn & into & unless that is the intended output. text/template gives direct, predictable text rendering.
Main Differences at a Glance
- Escaping:
html/templateescapes data for HTML contexts.text/templatedoes not. - Security focus:
html/templatereduces XSS risk in browser output. - API style: Both packages have similar parsing and execution methods.
- Best target:
html/templateis for HTML.text/templateis for plain text. - Trusted content:
html/templateallows trusted typed values such astemplate.HTML, but those should be rare.
The shared API is helpful. A developer can often switch imports from text/template to html/template with little code change. The catch is that unsafe helper functions and trusted content wrappers may still create issues. A search for template.HTML, template.JS, and template.URL is worth the few seconds it takes.
Context-Aware Escaping Explained
html/template does not escape every value in the same way. It looks at where the value lands.
For example, this template places data in different contexts:
<h1>{{.Title}}</h1>
<a href="/search?q={{.Query}}">Search</a>
<button data-user="{{.UserID}}">Open</button>
The title is escaped for HTML text. The query is escaped for a URL context. The user ID is escaped for an attribute context. This is the kind of quiet protection that saves time. Without it, every developer must remember the correct escaping rule for each spot. Expect to waste time on that during reviews if the wrong package is used.
When html/template Is Not Enough
html/template helps with output encoding, not all security needs. It cannot fix unsafe database queries. It cannot decide whether a user is allowed to view a page. It cannot sanitize rich text from a WYSIWYG editor by itself.
If an application accepts rich HTML, the team should sanitize it before storage or before rendering. Then, and only then, a carefully reviewed value may be marked with template.HTML. That type tells Go, this content is trusted. If that trust is wrong, the browser will receive unsafe markup.
Good practice is simple:
- Use
html/templatefor every server-rendered HTML page. - Keep user input as plain strings by default.
- Avoid trusted template types unless a sanitizer is in place.
- Review custom template functions for unsafe output.
- Add tests for suspicious input such as script tags and broken attributes.
Performance and Developer Experience
For most Go web applications, performance is not the deciding factor between these packages. Template parsing can be done at startup. Execution is usually fast enough for typical pages. The security model is the bigger reason to choose one over the other.
Teams often parse templates once and store them in memory. This avoids repeated disk reads and parsing work. During local development, some applications parse on every request so file changes appear at once. That feels convenient, even if it adds a small delay.
A practical setup may parse templates at startup in production and reload them in development. That keeps production stable and keeps local edits quick.
Practical Recommendation
The rule is easy: if the output will be read by a browser as HTML, use html/template. If the output is plain text, use text/template. This rule reduces confusion and keeps code reviews shorter.
Legacy projects should check imports first. Any web handler using text/template deserves attention. The fix may be as small as changing the import, but tests should confirm that pages still render correctly. Special cases, custom functions, and trusted HTML wrappers need extra review.
New Go web applications should start with html/template by default. It fits server-rendered pages, partials, layouts, and form views. It also pairs well with Go’s standard library style: boring, explicit, and hard to misuse when used as intended.
FAQ
Is html/template a replacement for text/template?
Not always. It is the right replacement when the output is HTML. For plain text, config files, generated code, or terminal output, text/template is usually a better fit.
Does html/template prevent all XSS attacks?
No. It reduces XSS risk through context-aware escaping. Unsafe JavaScript patterns, trusted HTML misuse, weak sanitization, and bad helper functions can still create problems.
Can a Go web app use both packages?
Yes. A single application may use html/template for web pages and text/template for plain text emails or generated files. The key is matching the package to the output format.
When should template.HTML be used?
Rarely. It should be used only when content has been sanitized or comes from a fully trusted source. It disables normal escaping for that value, so careless use can reintroduce XSS risk.
Which package should beginners choose for Go web pages?
Beginners should choose html/template. It has familiar template syntax and safer browser output. That default prevents many painful mistakes early in a project.