gohugoio/hugo · error

unable to process menus for page

Error message

unable to process menus for page %q: %w

What it means

Thrown by PageMenusFromPage (navigation/pagemenus.go:73) via the wrapErr closure when the page's front-matter 'menu' value cannot be interpreted as a string, a slice of strings, or a map of menu entries. The %w wraps the underlying error from hmaps.ToStringMapE (the value is not a map) or mapstructure.WeakDecode (a menu entry field has the wrong type). It names the offending page via p.Path().

Solutions

  1. Open the page named in the error message and fix its front-matter 'menu' field to be a string, a list of strings, or a map of menu-entry objects.
  2. Validate nested menu entries are maps with recognized keys (name, weight, parent, identifier, url, pre, post).
  3. Run hugo --templateMetricsHints or a local build to confirm the error clears after the edit.
  4. If the value is intentionally dynamic, move it into a valid structure under [menus] in the site config instead of page front matter.

Example fix

# before (invalid nested type)
---
menu:
  main: 42
---

# after
---
menu:
  main:
    name: "About"
    weight: 1
---
Defensive patterns

Strategy: validation

Validate before calling

// Validate page front-matter 'menu' before passing to PageMenusFromPage.
func validateMenuValue(ms any) error {
    switch v := ms.(type) {
    case string, []string, []any, map[string]any:
        return nil
    default:
        return fmt.Errorf("menu must be string, []string, or map; got %T", v)
    }
}

Type guard

func isValidMenuShape(ms any) bool {
    switch ms.(type) {
    case string, []string, []any, map[string]any, nil:
        return true
    }
    return false
}

Try / catch

pm, err := navigation.PageMenusFromPage(ms, p)
if err != nil {
    // err already wraps the page path; log and skip this page's menus.
    hugrot.Printf("skipping menus for %s: %v", p.Path(), err)
}

Prevention

When it happens

Trigger: A content file sets menu in front matter to an unsupported shape — e.g. menu = 123 (scalar number), menu = [["a"],["b"]] (nested slice), or a map whose nested entry is not itself a map (menu.main = "just-a-string-but-also-not". structured incorrectly). The cast.ToStringE and cast.ToStringSliceE both fail, then hmaps.ToStringMapE(ms) or ToStringMapE(menu) fails, triggering wrapErr.

Common situations: Switching front-matter format (YAML to TOML) and forgetting to adjust the menu table syntax; copy-pasting a menu config example that uses a different structure; providing menu: { main: [1,2,3] } where a nested object is expected; using a numeric or boolean value where a string/menu-map is required.

Related errors


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

Appendix: source

Thrown at navigation/pagemenus.go:73

	if err == nil {
		me.Menu = mname
		pm[mname] = &me
		return pm, nil
	}

	// Could be a slice of strings
	mnames, err := cast.ToStringSliceE(ms)

	if err == nil {
		for _, mname := range mnames {
			me.Menu = mname
			pm[mname] = &me
		}
		return pm, nil
	}

	wrapErr := func(err error) error {
		return fmt.Errorf("unable to process menus for page %q: %w", p.Path(), err)
	}

	// Could be a structured menu entry
	menus, err := hmaps.ToStringMapE(ms)
	if err != nil {
		return pm, wrapErr(err)
	}

	for name, menu := range menus {
		menuEntry := MenuEntry{Menu: name}
		if menu != nil {
			ime, err := hmaps.ToStringMapE(menu)
			if err != nil {
				return pm, wrapErr(err)
			}
			if err := mapstructure.WeakDecode(ime, &menuEntry.MenuConfig); err != nil {
				return pm, err
			}

View on GitHub (pinned to 52c9bd7908)