The template engine found a hole it cannot fill
You write a template with {{.Name}}. You run the server. The request hits the handler. The log prints template: main.html: "Name" is not defined and the response fails. You stare at the code. Name is right there in your data structure. The variable exists. The template references it. Go refuses to render.
The error means the template engine looked for a definition named Name in the current context and found nothing. It does not guess. It does not suggest similar names. It stops execution immediately. The template is a strict contract. If the data you pass does not match the variables the template demands, the contract is broken.
This error surfaces at runtime, not compile time. The Go compiler sees a string containing {{.Name}} and treats it as text. It cannot validate that the string matches the data structure you will pass later. The check happens when Execute runs. By then, the program is already live.
How the template engine resolves names
The text/template package uses a concept called the dot. The dot represents the current data context. When you call Execute(w, data), the dot starts as data. Every time the template encounters {{.X}}, the engine looks for X inside the dot.
If the dot is a map, the engine looks for the key X. If the dot is a struct, the engine looks for the field X. If the dot is a pointer, the engine dereferences it first. If the lookup fails, the engine emits the is not defined error.
Think of the template like a form with blank lines. The form asks for "Signature". You hand the form to a clerk with a clipboard. The clipboard has a list of values. If the list contains "Signed" but not "Signature", the clerk cannot fill the line. The clerk does not assume "Signed" means "Signature". The clerk rejects the form. The template engine is the clerk. It requires exact matches.
Minimal example: missing key in a map
The most common cause is a mismatch between the template variable and the data key. Maps are case-sensitive. Keys must match exactly.
package main
import (
"log"
"os"
"text/template"
)
func main() {
// The template expects a variable named "Title".
tmpl := template.Must(template.New("greeting").Parse("Hello, {{.Title}}!"))
// The map has "title" with a lowercase t.
// The template engine looks for "Title". It does not find it.
data := map[string]string{
"title": "World",
}
// Execute fails because "Title" is not in the map.
// The compiler rejects this at runtime with:
// template: greeting: "Title" is not defined
err := tmpl.Execute(os.Stdout, data)
if err != nil {
log.Fatal(err)
}
}
The fix is to align the keys. Change the map key to "Title" or change the template to {{.title}}. Consistency matters. The community convention is to use capitalized keys in maps when they represent exported concepts, though maps allow any string. Structs enforce capitalization for visibility.
Structs and the visibility trap
Structs are the standard way to pass data to templates. They provide type safety and clear structure. Structs introduce a visibility rule that causes is not defined errors for beginners.
Go uses capitalization to control visibility. Fields starting with a capital letter are exported. Fields starting with a lowercase letter are private. The template engine uses reflection to access fields. Reflection respects Go's visibility rules. The template cannot see private fields.
package main
import (
"log"
"os"
"text/template"
)
// User holds profile information.
// Name is exported. age is private.
type User struct {
Name string
age int
}
func main() {
// The template tries to access both Name and age.
tmpl := template.Must(template.New("profile").Parse("{{.Name}} is {{.age}} years old."))
user := User{
Name: "Alice",
age: 28,
}
// Execute fails on .age.
// The compiler rejects this at runtime with:
// template: profile: "age" is not defined
err := tmpl.Execute(os.Stdout, user)
if err != nil {
log.Fatal(err)
}
}
The error points to age. The field exists in the struct. The template engine simply cannot see it. The fix is to capitalize the field name. Rename age to Age. The template can then access {{.Age}}.
Unexported fields are invisible to templates. Capitalize or lose access.
Scoping: the dot moves
The dot is not static. Control structures change the dot. range and with blocks reassign the dot to iterate over values or narrow the context. This creates scope boundaries. Variables defined outside the block may become inaccessible inside.
When you enter a range block, the dot becomes the current item. When you enter a with block, the dot becomes the value of the expression. If you try to access a variable from the outer scope using {{.OuterVar}} inside the block, the engine looks for OuterVar on the inner dot. It fails.
package main
import (
"log"
"os"
"text/template"
)
func main() {
// The template accesses .Total inside a range.
// Inside range, the dot is the item, not the root data.
tmpl := template.Must(template.New("list").Parse(
"Total: {{.Total}}\n"+
"{{range .Items}}\n"+
" Item: {{.Name}}, Total: {{.Total}}\n"+
"{{end}}",
))
data := map[string]interface{}{
"Total": 100,
"Items": []struct {
Name string
}{
{Name: "Widget"},
{Name: "Gadget"},
},
}
// Execute fails inside the range.
// The compiler rejects this at runtime with:
// template: list: "Total" is not defined
err := tmpl.Execute(os.Stdout, data)
if err != nil {
log.Fatal(err)
}
}
The error occurs on the second {{.Total}} inside the range. The dot is now a struct with only Name. Total does not exist on that struct. The first {{.Total}} works because it is outside the range.
The fix is to use the dollar sign. $ always refers to the root data passed to Execute. It never changes. Use {{$.Total}} to access the root variable from inside any scope.
// Fixed template using $ to escape the scope.
tmpl := template.Must(template.New("list").Parse(
"Total: {{.Total}}\n"+
"{{range .Items}}\n"+
" Item: {{.Name}}, Total: {{$.Total}}\n"+
"{{end}}",
))
The dot moves. Track it. Use $ for global access.
Custom functions and FuncMap
Templates support custom functions. You register functions in a FuncMap and attach the map to the template before parsing. If you reference a function in the template that is not registered, you get the is not defined error.
Functions must be registered before Parse. The parser builds the AST and validates function calls. If a function is missing during parsing, the error happens immediately. If you register functions after parsing, the template still fails because the parser already rejected the call.
package main
import (
"log"
"os"
"text/template"
)
func main() {
// The template calls "upper" which is not a built-in function.
tmpl, err := template.New("text").Parse("{{upper .Input}}")
if err != nil {
// Parse fails immediately.
// The compiler rejects this with:
// template: text: "upper" is not defined
log.Fatal(err)
}
// This code never runs because Parse failed.
data := map[string]string{"Input": "hello"}
tmpl.Execute(os.Stdout, data)
}
The fix is to create a FuncMap, add the function, and chain .Funcs() before .Parse().
package main
import (
"log"
"os"
"strings"
"text/template"
)
func main() {
// Register custom functions before parsing.
funcMap := template.FuncMap{
"upper": strings.ToUpper,
}
// Chain Funcs before Parse.
// The parser now knows "upper" exists.
tmpl := template.Must(template.New("text").Funcs(funcMap).Parse("{{upper .Input}}"))
data := map[string]string{"Input": "hello"}
// Execute succeeds.
err := tmpl.Execute(os.Stdout, data)
if err != nil {
log.Fatal(err)
}
}
FuncMaps are the toolbox. Register them once, use them everywhere. The convention is to create a global FuncMap for your application and reuse it across all templates. Do not create a new FuncMap per request. Registration is a setup step.
Pitfalls and runtime behaviors
Several edge cases trigger this error or related failures. Understanding them prevents debugging loops.
Typos are the simplest cause. {{.Nmae}} fails if the field is Name. Go templates are case-sensitive. Name and name are different. The error message shows the exact string the template used. Check the spelling in the template against the data key or field name.
Nil pointers can cause confusion. If you pass a nil pointer to a struct, the template engine handles field access gracefully in some cases. Accessing a field on a nil struct pointer returns the zero value for that field, not an error. However, calling a method on a nil pointer panics. The error is not defined usually means the name lookup failed, not that the value is nil. If you see a panic about nil pointer, that is a different issue.
Interface values behave like their concrete types. If you pass a struct wrapped in an interface, the template sees the struct fields. If you pass an interface with methods, the template sees the methods. The error occurs if the interface does not satisfy the expected shape.
Nested access stops at the first missing link. {{.Address.City}} fails if Address is missing. The error points to Address, not City. The engine stops at the first undefined variable. Fix the parent before checking the child.
The worst template bug is the one that swallows errors. Always check the error return from Execute. Logging the error reveals the exact variable name and line number. Ignoring the error hides the problem until the HTML is broken.
Decision: when to use data structures and functions
Choose the right tool for passing data and extending templates. The choice affects readability, safety, and error rates.
Use a struct when you have a fixed set of fields and want type safety. Structs enforce capitalization rules and make the data shape explicit. Use structs for most template data.
Use a map when the data shape is dynamic or comes from an external source like a database row. Maps allow flexible keys but lose type checking. Use maps for dynamic content.
Use a FuncMap when you need custom logic in templates. FuncMaps add functions like formatting, math, or lookups. Use FuncMaps to keep templates declarative. Avoid complex logic in templates; put it in functions.
Use the dollar sign when you need to access root data from inside a scope. $ escapes range and with blocks. Use $ to preserve context across iterations.
Use ExecuteTemplate when you have multiple named templates in one tree and need to render a specific one. Execute renders the template named in the New call. Use ExecuteTemplate to select sub-templates.