gohugoio/hugo · error
html/template: cannot Parse after Execute
Error message
html/template: cannot Parse after Execute
What it means
Returned by checkCanParse, which guards Parse, AddParseTree, ParseFiles, ParseGlob and ParseFS. Once a template's nameSpace.escaped flag is true (set by escape() on the first Execute), adding or redefining templates is forbidden because html/template applies context-aware escaping to the complete set and cannot safely escape newly added nodes afterward.
Source
Thrown at tpl/internal/go_templates/htmltemplate/template.go:91
// "missingkey=zero"
// The operation returns the zero value for the map type's element.
// "missingkey=error"
// Execution stops immediately with an error.
func (t *Template) Option(opt ...string) *Template {
t.text.Option(opt...)
return t
}
// checkCanParse checks whether it is OK to parse templates.
// If not, it returns an error.
func (t *Template) checkCanParse() error {
if t == nil {
return nil
}
t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock()
if t.nameSpace.escaped {
return fmt.Errorf("html/template: cannot Parse after Execute")
}
return nil
}
// escape escapes all associated templates.
func (t *Template) escape() error {
t.nameSpace.mu.Lock()
defer t.nameSpace.mu.Unlock()
t.nameSpace.escaped = true
if t.escapeErr == nil {
if t.Tree == nil {
return fmt.Errorf("template: %q is an incomplete or empty template", t.Name())
}
if err := escapeTemplate(t, t.text.Root, t.Name()); err != nil {
return err
}
} else if t.escapeErr != escapeOK {
return t.escapeErrView on GitHub (pinned to 52c9bd7908)
Solutions
- Finish all Parse/ParseFiles/ParseGlob/ParseFS calls before the first Execute on the template or any associated template.
- If you need to add templates after rendering, Clone the original before executing and add templates to the clone.
- Restructure so parsing is a one-time init phase separated from the render phase.
- Use a fresh New(name) template set for each render cycle instead of mutating an executed one.
Example fix
// before
t.Execute(w, data)
_, err := t.Parse(`{{define "x"}}new{{end}}`) // error
// after
_, err := t.Parse(`{{define "x"}}new{{end}}`) // parse first
if err != nil { return err }
t.Execute(w, data) Defensive patterns
Strategy: validation
Validate before calling
// Enforce parse-before-execute ordering at the call site.
parsed, executed := false, false
func parseThenExec(t *template.Template, body string, w io.Writer, data any) error {
if executed {
return fmt.Errorf("cannot Parse after Execute; rebuild the set")
}
if _, err := t.Parse(body); err != nil { return err }
parsed = true
executed = true
return t.Execute(w, data)
} Try / catch
if _, err := t.Parse(body); err != nil {
if strings.Contains(err.Error(), "cannot Parse after Execute") {
// start a fresh set instead of mutating the executed one
t = template.New(t.Name())
_, err = t.Parse(body)
}
if err != nil { return err }
} Prevention
- Segregate phases: parse everything first, then execute.
- For dynamic additions, clone-then-parse rather than parse-into-executed.
- Add a lint/test asserting no Parse call follows an Execute on the same set.
When it happens
Trigger: Any call to t.Parse, t.AddParseTree, t.ParseFiles, t.ParseGlob, or t.ParseFS (and the package-level ParseFiles/ParseGlob/ParseFS) on an html/template Template whose nameSpace.escaped is already true from a prior Execute/ExecuteTemplate.
Common situations: Hot-reloading or live-parsing templates into a set that was already rendered; calling Execute during setup (e.g. to warm caches) then Parse to add more partials; migrating from text/template (which allows post-execute parse) to html/template.
Related errors
- html/template: cannot Clone %q after it has executed
- template: %q is an incomplete or empty template
- html/template: %q is an incomplete template
- html/template: cannot Clone %q after it has executed
- html/template: %q is undefined
AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09).
Data as JSON: /api/errors/ca94788aa8031635.
Report an issue: GitHub.