Skip to Content
Course content

196: Template Functions and Pipelines

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this happen to almost every dev moving from simple HTML templates to more complex logic in Go. You want to do something simple—like formatting a date or truncating a long string—so you write a helper function, add it to your FuncMap, and hit run. Then, the whole thing crashes with a "function not defined" error.

package main

import (
	"html/template"
	"os"
)

func truncate(s string) string {
	if len(s) > 20 {
		return s[:17] + "..."
	}
	return s
}

func main() {
	funcMap := template.FuncMap{
		"truncate": truncate,
	}

	// This looks correct, right?
	tmpl, _ := template.New("web").Parse(`
{{ .Body | truncate }}
`) tmpl.Funcs(funcMap) // Adding the functions here tmpl.Execute(os.Stdout, map[string]string{"Body": "This is a very long piece of text that should be truncated."}) }

The "Function Not Defined" Panic

If you run the code above, it fails. You'll see a panic or an error stating that the function truncate is not defined. At first glance, it feels like a lie. You clearly defined truncate in your FuncMap and you called tmpl.Funcs(funcMap). So why does the template engine claim it doesn't exist?

The issue is the order of operations. In Go, the Parse method doesn't just store the string for later; it actually parses the template logic immediately to ensure the syntax is valid. When Parse hits {{ .Body | truncate }}, it looks at the current FuncMap to see if truncate is a known entity. Because we called Parse before Funcs, the map was empty during the parsing phase. The template engine fails the build before it ever gets to the execution phase.

Registering Before Parsing

The fix is deceptively simple: you must register your functions before you parse the template string or file. I usually wrap this in a helper variable to keep the chain clean.

func main() {
	funcMap := template.FuncMap{
		"truncate": truncate,
	}

	// Fix: New -> Funcs -> Parse
	tmpl, err := template.New("web").Funcs(funcMap).Parse(`<div>{{ .Body | truncate }}</div>`)
	if err != nil {
		panic(err)
	}

	tmpl.Execute(os.Stdout, map[string]string{"Body": "This is a very long piece of text that should be truncated."})
}

By chaining .Funcs(funcMap) before .Parse(...), the parser now has a dictionary to reference. Now, when it sees truncate, it says, "Okay, I don't know how to run this yet, but I know it exists and it's valid," and it lets the program proceed.

Chaining Logic with Pipelines

Now that we have functions working, let's talk about the | symbol. In Go templates, this is called a pipeline. It works exactly like a Unix pipe: the output of the expression on the left becomes the final argument of the function on the right.

Imagine you want to clean up a user's bio: you want to trim the whitespace, convert it to lowercase, and then truncate it. Instead of nesting functions like a nightmare of parentheses—{{ truncate (toLower (trim .Bio)) }}—you use a pipeline:

{{ .Bio | trim | toLower | truncate }}

I find this much more readable. The data flows from left to right. One thing to keep in mind: if your function requires multiple arguments, the piped value is always added as the last argument. So if you have a function substring(start, end, text), you'd call it as {{ .Content | substring 0 10 }}. The .Content is passed in as the third argument, not the first.




📋 Practical Task

Build a Custom Product Price Formatter

You are building a product listing page. You need to create a template that takes a raw float64 price and processes it through a pipeline to make it "customer-ready."

Requirements:

  • Create a FuncMap with two functions:
    1. addCurrency: Takes a string and prepends "$".
    2. roundPrice: Takes a float64 and returns it as a string formatted to two decimal places (use fmt.Sprintf).
  • Set up a template that uses a pipeline to first round the price and then add the currency symbol (e.g., {{ .Price | roundPrice | addCurrency }}).
  • Ensure the functions are registered before the template is parsed to avoid the panic discussed in the lesson.
  • Execute the template with a price like 123.4567, which should result in "$123.46".
Rating
0 0

There are no comments for now.

to be the first to leave a comment.