How to use html template package

Import the html/template package from the Go standard library and use template.New().Parse() to create safe, dynamic HTML content.

The dashboard that injected scripts

You are building a user profile page. You have a struct with a name and a bio. You want to render it in the browser. You reach for string concatenation or fmt.Sprintf. You paste {{.Name}} into a string. It works until a user named <script>alert('xss')</script> signs up. Suddenly your dashboard is executing JavaScript. The html/template package exists to stop this before it happens. It is not just a templating engine. It is a security layer that understands HTML structure and escapes values based on where they appear.

Context-aware escaping

Go splits templating into two packages. text/template generates plain text. It performs no escaping. html/template is built on top of text/template but adds automatic escaping. When you render HTML, the engine tracks the context of every value. If a value ends up inside a <div>, it escapes HTML entities. If the same value ends up inside a <script> tag, it escapes using JavaScript-safe sequences. This is called context-aware escaping. You write the template once. The engine handles the safety.

Minimal example

Here is the simplest flow: create a template, parse the string, execute with data.

package main

import (
	"html/template"
	"log"
	"os"
)

func main() {
	// New creates a template instance with a name for error reporting.
	// Parse reads the template string and compiles it into an AST.
	tmpl, err := template.New("greeting").Parse("<h1>Hello, {{.Name}}!</h1>")
	if err != nil {
		// Fatal exits the program. In real code, return the error.
		log.Fatal(err)
	}

	// Data maps to the template root. Dot notation accesses fields.
	// The map key "Name" matches {{.Name}} in the template.
	data := map[string]string{"Name": "Alice"}

	// Execute writes the rendered result to os.Stdout.
	// It returns an error if rendering fails or the writer fails.
	err = tmpl.Execute(os.Stdout, data)
	if err != nil {
		log.Fatal(err)
	}
}

Walkthrough

Parsing happens at runtime by default. template.New().Parse() returns a *template.Template. This object holds the compiled abstract syntax tree. You can parse once and execute many times. The template is thread-safe for execution. Parsing is not thread-safe. If you parse in a handler, you are doing work on every request. Parse once at startup. Execute many times.

Methods on *template.Template chain because they return the receiver. The receiver name is conventionally a short variable like t. You will see (t *Template) Funcs(...) in the source, not (self *Template). This is a Go convention: receiver names match the type initial.

Parse once. Execute often. Templates are compiled code, not just strings.

Context-aware escaping in action

Context-aware escaping adapts to where the value appears. The engine inspects the template structure to determine the output context.

package main

import (
	"html/template"
	"log"
	"os"
)

func main() {
	// Template with both HTML context and script context.
	// The engine tracks context changes across tags.
	const src = `
	<div>{{.Value}}</div>
	<script>var x = "{{.Value}}";</script>
	`

	// Must panics if the error is non-nil.
	// Use this only for templates known to be correct at compile time.
	tmpl := template.Must(template.New("ctx").Parse(src))

	// Input contains HTML tags that could break structure.
	data := map[string]string{"Value": "<b>bold</b>"}

	// Execute renders to stdout.
	// Observe the different escaping strategies in the output.
	tmpl.Execute(os.Stdout, data)
}

The output shows <div>&lt;b&gt;bold&lt;/b&gt;</div> and <script>var x = "\x3cb\x3ebold\x3c/b\x3e";</script>. In the div, angle brackets become HTML entities. In the script, they become hex escapes. The engine knows HTML. You do not need to escape manually.

Realistic example

Real templates use structs for type safety and live in files. Maps lose type safety and hide missing fields until runtime. Structs give you compile-time checks.

// User holds profile data.
// Only exported fields appear in the template.
// Lowercase fields are invisible to the template engine.
type User struct {
	Name string
	Role string
}

// renderUser writes the user profile to the response.
// It parses the template on every request for this example.
func renderUser(w http.ResponseWriter, r *http.Request) {
	// ParseFiles compiles templates from disk.
	// It returns a template set containing all parsed files.
	tmpl, err := template.ParseFiles("user.html")
	if err != nil {
		// Internal server error if the template is broken.
		// Log the error for debugging, hide details from the user.
		http.Error(w, "Internal Error", http.StatusInternalServerError)
		return
	}

	// Struct fields map to template variables.
	// .Name accesses the Name field. .Role accesses the Role field.
	data := User{Name: "Bob", Role: "Admin"}

	// Execute renders the template to the HTTP response.
	// The browser receives the rendered HTML.
	err = tmpl.Execute(w, data)
	if err != nil {
		log.Printf("render error: %v", err)
	}
}

The server setup is standard library boilerplate.

func main() {
	// Mount the handler on the root path.
	http.HandleFunc("/user", renderUser)

	// Start the server.
	// ListenAndServe blocks until the process exits.
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Handlers should accept context.Context as the first argument. Pass it through to template functions if they perform I/O. Functions that take a context should respect cancellation and deadlines.

Structs over maps. Files over strings. Parse once, execute many.

Custom functions

Extend the template language with custom functions. Built-in functions like eq, if, and range cover most logic. Custom functions let you add domain-specific helpers.

package main

import (
	"html/template"
	"log"
	"os"
	"strings"
)

func main() {
	// FuncMap defines custom functions available in the template.
	// Keys are function names. Values are function values.
	funcMap := template.FuncMap{
		"upper": strings.ToUpper,
	}

	// New creates the template. Funcs adds the map. Parse compiles.
	// Methods chain because they return the receiver.
	tmpl := template.New("custom").Funcs(funcMap).Parse("{{upper .Name}}")

	// Data is a map with a Name key.
	data := map[string]string{"Name": "go"}

	// Execute renders the template.
	// The upper function transforms the value at render time.
	tmpl.Execute(os.Stdout, data)
}

Functions run at render time. Keep them pure and fast.

Pitfalls

Private fields are invisible. If a struct field starts with a lowercase letter, the template cannot see it. The compiler does not catch this. The template renders an empty value. Use exported fields for template data.

Escaping is your friend. Do not use template.HTML unless you have trusted content. template.HTML disables escaping. If you wrap user input in template.HTML, you re-enable XSS. Only use it for content you control, like static assets or sanitized HTML from a rich text editor.

template.Must hides errors. Must panics if the error is non-nil. Use it only for templates embedded at compile time or known to be correct. If you use Must on a template loaded from a user-provided file, a syntax error crashes your server. Handle the error explicitly.

The compiler rejects this with undefined variable if you reference a missing field in a strict context. Runtime panics occur if you pass nil data to a template that expects a struct. Always check errors from Parse and Execute. The boilerplate is visible. It forces you to handle errors.

Named templates require matching names. When you parse multiple files, you get a set of named templates. Use ExecuteTemplate to pick which one to render. The name must match the filename or the {{define}} name. The compiler complains with template: "name": define error if the name does not exist.

Private fields are invisible. Escaping is your friend. Trust the engine.

Decision matrix

Use html/template when rendering HTML for a browser. Use text/template when generating non-HTML text like SQL, JSON, or configuration files. Use template.HTML only when you have trusted content and explicitly want to bypass escaping. Use string concatenation for trivial snippets where template overhead is unjustified. Use a third-party engine when you need advanced features like template inheritance or partials that the standard library lacks.

Pick the tool that matches the output format. Safety comes first.

Where to go next