gohugoio/hugo · error

failed to transform template

Error message

failed to transform template %q: %w

What it means

applyTemplateTransformers (templatetransform.go:71-91) runs an AST transformation pass over each parsed template to inject partial-return wrappers, deferred nodes, and partial-decorator handling. If applyTransformationsAndSetReturnWrapper returns an error, it is wrapped with the template name. This is a build-time parse/transform failure on a specific template.

Solutions

  1. Read the wrapped (%w) error to find the specific transformation that failed.
  2. Inspect the named template for unsupported constructs (e.g. {{ inner }} inside a with-partial, bad $_hugo_config).
  3. Simplify the template to isolate which construct triggers the transform failure.
  4. Update Hugo; transform logic is frequently patched.
Defensive patterns

Strategy: try-catch

Try / catch

// In Go tests/builds, unwrap to find the specific sub-transform failure:
if err != nil && strings.Contains(err.Error(), "failed to transform template") {
    // the %w holds the real cause (e.g. $_hugo_config decode, inner-in-with)
    log.Fatalf("transform failed for template: %v", err)
}

Prevention

When it happens

Trigger: A template whose parsed AST cannot be transformed — typically because a nested transformation (config decode, partial decorator creation, inner-insertion) failed. The wrapped error identifies the specific sub-failure; the outer message identifies which template it happened in.

Common situations: A malformed $_hugo_config header; a {{ with }} block wrapping a partial decorator that contains an unsupported construct; syntax that parses but is semantically invalid during the transform pass; Hugo version regression in the transformer.

Related errors


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

Appendix: source

Thrown at tpl/tplimpl/templatetransform.go:88

func applyTemplateTransformers(
	t *TemplInfo,
	store *TemplateStore,
	lookupFn func(name string, in *TemplInfo) *TemplInfo,
) (*templateTransformContext, error) {
	if t == nil {
		return nil, errors.New("expected template, but none provided")
	}

	c := newTemplateTransformContext(t, store, lookupFn)
	c.t.ParseInfo = defaultParseInfo
	tree := getParseTree(t.Template)
	if tree == nil {
		panic(fmt.Errorf("template %s not parsed", t))
	}

	if err := c.applyTransformationsAndSetReturnWrapper(tree); err != nil {
		return c, fmt.Errorf("failed to transform template %q: %w", t.Name(), err)
	}

	return c, c.err
}

func getParseTree(templ tpl.Template) *parse.Tree {
	if text, ok := templ.(*texttemplate.Template); ok {
		return text.Tree
	}
	return templ.(*htmltemplate.Template).Tree
}

const (
	// We parse this template and modify the nodes in order to assign
	// the return value of a partial to a contextWrapper via Set. We use
	// "range" over a one-element slice so we can shift dot to the
	// partial's argument, Arg, while allowing Arg to be falsy.
	partialReturnWrapperTempl = `{{ $_hugo_dot := $ }}{{ $ := .Arg }}{{ range (slice .Arg) }}{{ $_hugo_dot.Set ("PLACEHOLDER") }}{{ end }}`

View on GitHub (pinned to 52c9bd7908)