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.escapeErr

View on GitHub (pinned to 52c9bd7908)

Solutions

  1. Finish all Parse/ParseFiles/ParseGlob/ParseFS calls before the first Execute on the template or any associated template.
  2. If you need to add templates after rendering, Clone the original before executing and add templates to the clone.
  3. Restructure so parsing is a one-time init phase separated from the render phase.
  4. 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

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


AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09). Data as JSON: /api/errors/ca94788aa8031635. Report an issue: GitHub.