Template Functions

A FuncMap gives a template named helpers to call, so formatting lives in one place instead of in every handler.

Syntaxfunc (t *Template) Funcs(funcMap FuncMap) *Template

A template can only call functions it has been handed, plus a fixed set of built-ins. Those built-ins are and, call, html, index, slice, js, len, not, or, print, printf, println, urlquery, and the comparisons eq, ne, lt, le, gt, ge. Since Go 1.18, and and or stop evaluating arguments once the result is known. Anything else you supply.

FuncMap

template.FuncMap maps a name to a function, and Funcs attaches it to a template. The signature is func (t *Template) Funcs(funcMap FuncMap) *Template; it returns the template so calls chain. Each function must return one value, or two values whose second is an error. If that error comes back non-nil, execution stops and Execute returns it. Funcs panics if a value is not a function of an acceptable shape, or if the name cannot be used as a function name in a template. That panic happens at startup, which is where you want it.

Keep formatting out of the handler

A handler that builds display strings ends up carrying currency symbols and date layouts. Give the template a money function instead and pass it plain values: an int of cents, a time.Time. One function then formats every price on the site.

Shorthand and longhand

A call and its pipe form are the same thing, because a pipe passes the left value as the last argument:

{{money .Cents}}
{{.Cents | money}}

Keep these functions cheap and free of side effects. A function that queries a database runs in the middle of rendering, where an error is already hard to report.

Example

Example · go
package main

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

// money turns a count of cents into a display string.
func money(cents int) string {
	return fmt.Sprintf("$%d.%02d", cents/100, cents%100)
}

// day formats a date. Go's layouts spell out the reference time, 2 Jan 2006.
func day(t time.Time) string {
	return t.Format("2 Jan 2006")
}

var funcs = template.FuncMap{
	"money": money,
	"day":   day,
}

const receiptHTML = `<p>{{.Item}}: {{money .Cents}} on {{day .When}}</p>
`

type line struct {
	Item  string
	Cents int
	When  time.Time
}

func main() {
	// Funcs comes before Parse, always.
	t := template.Must(template.New("receipt").Funcs(funcs).Parse(receiptHTML))
	l := line{Item: "Notebook", Cents: 1250, When: time.Date(2026, time.March, 4, 0, 0, 0, 0, time.UTC)}
	if err := t.Execute(os.Stdout, l); err != nil {
		log.Fatal(err)
	}
}

// Output:
// <p>Notebook: $12.50 on 4 Mar 2026</p>

When to use it

  • Format money, dates and durations the same way on every page.
  • Truncate long text for a listing page without shortening it in the handler.
  • Expose a URL-building helper so links survive a route change.
  • Join a slice of tags into a comma-separated string inside the template.

More examples

A function that can fail

The two-value form is for cases where the data can be wrong, like a division by zero. Change Total to 0 and Execute returns an error naming the template, the position and the function, and the page stops mid-render. That is the reason to buffer output: with Total at 0 a direct write would already have sent the opening tags.

Example · go
package main

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

// A template function may return (value, error). A non-nil error stops
// execution and is returned from Execute.
func pct(part, whole int) (string, error) {
	if whole == 0 {
		return "", errors.New("pct: whole is zero")
	}
	return fmt.Sprintf("%d%%", part*100/whole), nil
}

const statHTML = `<p>{{pct .Done .Total}} complete</p>
`

type stat struct {
	Done  int
	Total int
}

func main() {
	t := template.Must(template.New("stat").Funcs(template.FuncMap{"pct": pct}).Parse(statHTML))
	if err := t.Execute(os.Stdout, stat{Done: 3, Total: 4}); err != nil {
		log.Fatal(err)
	}
}

// Output:
// <p>75% complete</p>

The built-ins, before you add your own

Check the built-in list before writing a helper. printf is the whole of fmt's formatting, index reaches into a slice or map by key, and slice takes a sub-slice the same way the language does. Most of the small helpers people add to a FuncMap, for rounding a number or picking an element, already exist here.

Example · go
package main

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

// No FuncMap at all. printf, len, index and slice are always available.
const srcHTML = `<p>{{printf "%.2f" .Rate}}</p>
<p>{{len .Tags}} tags, first {{index .Tags 0}}</p>
<p>{{range slice .Tags 1 3}}{{.}} {{end}}</p>
`

type view struct {
	Rate float64
	Tags []string
}

func main() {
	t := template.Must(template.New("view").Parse(srcHTML))
	v := view{Rate: 0.5, Tags: []string{"go", "http", "sql"}}
	if err := t.Execute(os.Stdout, v); err != nil {
		log.Fatal(err)
	}
}

// Output:
// <p>0.50</p>
// <p>3 tags, first go</p>
// <p>http sql </p>

One FuncMap for the whole application

Put {{upper .Name}} and {{join .Tags ", "}} in templates/home.html and curl http://localhost:8080/. The ordering matters: template.New(...).Funcs(...).ParseGlob(...) registers the functions first, whereas template.ParseGlob(...) on its own gives you no hook to add them and fails at parse time on the first unknown name.

Example · go
package main

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

// Defined once and attached to the set before any file is parsed. Ordinary
// standard library functions qualify: ToUpper returns one value.
var funcs = template.FuncMap{
	"upper": strings.ToUpper,
	"join":  strings.Join,
}

var pages = template.Must(template.New("pages").Funcs(funcs).ParseGlob("templates/*.html"))

func handle(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	data := map[string]any{"Name": "ada", "Tags": []string{"go", "sql"}}
	if err := pages.ExecuteTemplate(w, "home.html", data); err != nil {
		log.Print(err)
	}
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /", handle)
	log.Fatal(http.ListenAndServe(":8080", mux))
}

Discussion

  • Be the first to comment on this lesson.