How to Use text/template in Go

Web
Use text/template to parse a template string and execute it with data to generate dynamic text output.

Generating text with templates

You wrote a CLI tool that configures a database. The user runs myapp init, and you need to generate a config.yaml file with their username, port number, and a list of allowed hosts. Hardcoding the string with concatenation works for one line. It falls apart when you have indentation, loops, or conditional blocks. You need a template engine. Go ships with one in the standard library.

text/template parses a template string, executes it with data, and writes the result to an output stream. It handles the heavy lifting of interpolation, iteration, and control flow so you don't have to write fragile string manipulation code.

Think of a template as a fill-in-the-blank form. You write the static text once, leaving placeholders for the dynamic parts. At runtime, you hand the form a bag of data. The engine fills in the blanks and hands back the completed document. text/template is safe by default: if you ask for a field that doesn't exist, it prints nothing instead of crashing. It's also strict about types, which keeps things predictable.

Minimal example

Here's the simplest template: parse a string, execute with a map, write to stdout.

// Executing a template with a map and printing to stdout.
package main

import (
	"os"
	"text/template"
)

func main() {
	// Define the template string with a placeholder for Name.
	tmpl := `Hello, {{.Name}}!`
	// Parse the string into a template object. "greeting" is the name.
	t, err := template.New("greeting").Parse(tmpl)
	if err != nil {
		// Parse errors usually mean a syntax mistake in the template.
		panic(err)
	}
	// Data is a map matching the keys in the template.
	data := map[string]string{"Name": "World"}
	// Execute writes the result to os.Stdout.
	err = t.Execute(os.Stdout, data)
	if err != nil {
		panic(err)
	}
}

How it works

When you call template.New("name").Parse(str), Go compiles the template string into an internal tree structure. This happens at runtime, not compile time. The compiler checks your Go code, but the template syntax is validated when Parse runs. If the template has a mismatched {{if}} or a typo in a function name, Parse returns an error. You must handle that error.

The Execute method walks the tree, evaluates the expressions against the data, and writes bytes to the io.Writer you provide. The dot {{.}} represents the current data context. In {{.Name}}, the dot is the map, and Name is the key. If the data is a struct, Name is the exported field. If the field is unexported, the template cannot see it.

Templates are declarative. They describe what to output, not how to compute it. Logic belongs in Go functions. Templates format data.

Realistic example

Here's a template that iterates a slice and conditionally formats output.

// Generating a list with range and if blocks.
package main

import (
	"os"
	"text/template"
)

type User struct {
	Name   string
	Active bool
}

func main() {
	// Range iterates; if checks status.
	tmpl := `{{range .}}- {{.Name}}
{{if .Active}}  [ACTIVE]
{{end}}{{end}}`
	// Parse compiles syntax; Execute writes to stdout.
	t, err := template.New("list").Parse(tmpl)
	if err != nil {
		panic(err)
	}
	// Execute with inline slice data.
	err = t.Execute(os.Stdout, []User{{Name: "Alice", Active: true}, {Name: "Bob", Active: false}})
	if err != nil {
		panic(err)
	}
}

range iterates over slices, maps, and arrays. Inside the loop, the dot changes to the current element. if checks boolean values. Zero values for numbers, empty strings, and nil pointers evaluate to false.

Custom functions

Templates have built-in functions like printf, eq, and len. You can add your own with FuncMap. This keeps logic in Go and formatting in the template.

// Adding a custom function to transform data.
package main

import (
	"os"
	"strings"
	"text/template"
)

func main() {
	// FuncMap registers Go functions for use in the template.
	funcMap := template.FuncMap{
		"upper": strings.ToUpper,
	}
	// Funcs attaches the map before parsing.
	t, err := template.New("upper").Funcs(funcMap).Parse(`{{.Name | upper}}`)
	if err != nil {
		panic(err)
	}
	// Pipe passes the value to the custom function.
	err = t.Execute(os.Stdout, map[string]string{"Name": "alice"})
	if err != nil {
		panic(err)
	}
}

The pipe operator | passes the left value as the first argument to the function on the right. You can chain pipes: {{.Name | upper | printf "%s!"}}. Functions must be safe for templates. Avoid functions with side effects or non-deterministic output.

Multiple templates

You can define multiple named templates in one parse call. This enables includes and layout composition.

// Defining multiple named templates in one string.
package main

import (
	"os"
	"text/template"
)

func main() {
	// Define blocks create named templates within the same object.
	tmpl := `{{define "header"}}HEADER{{end}}
{{define "body"}}BODY{{end}}`
	// Parse compiles all defined templates.
	t, err := template.New("root").Parse(tmpl)
	if err != nil {
		panic(err)
	}
	// ExecuteTemplate renders a specific named template.
	err = t.ExecuteTemplate(os.Stdout, "header", nil)
	if err != nil {
		panic(err)
	}
}

{{define "name"}} creates a template named name. ExecuteTemplate selects which one to render. You can also use {{template "name"}} inside another template to include it. This is how you build reusable components without duplicating text.

Pitfalls and errors

Missing data vanishes silently. If you write {{.Missing}} and the data has no Missing field, the output is empty. No error. This prevents crashes on optional data. Check your data structure, not the engine, when output is blank.

Type mismatches cause runtime errors. If you pass a string but the template expects a map, Execute returns an error. The compiler rejects this with template: list:3: unexpected . in operand. Always validate data types before execution.

Parse errors happen when syntax is invalid. A missing {{end}} gives template: list:3: unexpected end of template. Fix the template string.

text/template does no escaping. It outputs data exactly as provided. If you render user input into HTML, you risk injection attacks. Use html/template for HTML or XML. html/template automatically escapes dangerous characters. text/template is for plain text, config files, and source code.

Convention aside: gofmt formats Go code, not template strings. Template strings are just strings. Indentation inside backticks is preserved literally. Use {{- and -}} to trim whitespace around actions if needed.

Templates are safe by default. Missing data vanishes silently. Check your data, not the engine.

Decision matrix

Use text/template when generating plain text, config files, or source code where escaping is not needed. Use html/template when rendering HTML or XML to prevent injection attacks via automatic escaping. Use string concatenation or fmt.Sprintf for simple one-line strings with few variables. Use a dedicated templating library like gomplate or jet when you need complex inheritance, layout composition, or custom function registries beyond the standard library.

Where to go next