gohugoio/hugo · error

maximum template call stack size exceeded in

Error message

maximum template call stack size exceeded in %q

What it means

Hugo guards against infinite recursion in template execution by tracking the nesting level of partial/template calls. In ExecuteWithContextAndKey (templatestore.go:539-541) a hard threshold of 999 is enforced: each nested ExecuteWithContext increments the level via the CurrentTemplateInfo parent chain, and when it exceeds 999 the named template's filename is reported. This prevents stack overflows from mutually-recursive partials or templates that include themselves.

Solutions

  1. Identify the template named in the error and trace its include chain to find the cycle.
  2. Add a base-case guard in the recursive partial (e.g. {{ if .Pages }}...{{ partial "nav" . }}{{ end }} or pass a depth counter and stop recursing).
  3. Use Hugo's built-in constructs (range over .Pages) instead of manual recursion through partials.
  4. Temporarily enable template metrics/instrumentation to see which partial is invoked repeatedly.

Example fix

// before — recursive partial layouts/partials/nav.html
{{ partial "nav" . }}

// after — guard against nil/empty to terminate recursion
{{ with .Pages }}
  {{ range . }}
    {{ partial "nav" . }}
  {{ end }}
{{ end }}
Defensive patterns

Strategy: validation

Validate before calling

// In a recursive partial, always guard the recursion before calling partial
{{ with .Pages }}
  {{ range . }}
    {{ partial "nav" . }}
  {{ end }}
{{ end }}

// Or pass a depth counter and stop at a safe limit
{{ if lt .Depth 50 }}
  {{ partial "nav" (dict "node" .Node "depth" (add .Depth 1)) }}
{{ end }}

Prevention

When it happens

Trigger: A template partial calls itself directly or transitively (e.g. partial A includes partial B which includes partial A), or a layout includes itself via {{ template "_default/list.html" . }}. Triggered during site build/render when ExecuteWithContextAndKey recurses past level 999.

Common situations: A recursive partial used to render a tree/menu structure (e.g. nav) without a base-case termination check; a baseof/layout that accidentally re-includes itself after a rename or refactor; upgrading Hugo versions where partial context passing changed and a guard condition now never hits nil.

Related errors


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

Appendix: source

Thrown at tpl/tplimpl/templatestore.go:541

	templ := ti.Template

	parent := tpl.Context.CurrentTemplate.Get(ctx)
	var level int
	if parent != nil {
		level = parent.Level + 1
	}
	currentTi := &tpl.CurrentTemplateInfo{
		Parent:                 parent,
		Level:                  level,
		Key:                    key,
		CurrentTemplateInfoOps: ti,
	}

	ctx = tpl.Context.CurrentTemplate.Set(ctx, currentTi)

	const levelThreshold = 999
	if level > levelThreshold {
		return fmt.Errorf("maximum template call stack size exceeded in %q", ti.Filename())
	}

	if t.opts.Metrics != nil {
		defer t.opts.Metrics.MeasureSince(templ.Name(), time.Now())
	}

	execErr := t.storeSite.executer.ExecuteWithContext(ctx, ti, wr, data)
	if execErr != nil {
		return t.addFileContext(ti, "execute of template failed", execErr)
	}
	return nil
}

func (t *TemplateStore) GetFunc(name string) (reflect.Value, bool) {
	v, found := t.storeSite.execHelper.funcs[name]
	return v, found
}

View on GitHub (pinned to 52c9bd7908)