gohugoio/hugo · error

destination must be a map, got %T

Error message

destination must be a map, got %T

What it means

Hugo's `merge` template function only supports maps. Merge iterates parameters in reverse and merges each source into the last parameter (the destination); if the destination value's reflect.Kind is not a map, merge aborts with this error, reporting the actual Go type it received.

Source

Thrown at tpl/collections/merge.go:53

	var err error
	result := params[len(params)-1]

	for i := len(params) - 2; i >= 0; i-- {
		result, err = ns.merge(params[i], result)
		if err != nil {
			return nil, err
		}
	}

	return result, nil
}

// merge creates a copy of dst and merges src into it.
func (ns *Namespace) merge(src, dst any) (any, error) {
	vdst, vsrc := reflect.ValueOf(dst), reflect.ValueOf(src)

	if vdst.Kind() != reflect.Map {
		return nil, fmt.Errorf("destination must be a map, got %T", dst)
	}

	if !hreflect.IsTruthfulValue(vsrc) {
		return dst, nil
	}

	if vsrc.Kind() != reflect.Map {
		return nil, fmt.Errorf("source must be a map, got %T", src)
	}

	if vsrc.Type().Key() != vdst.Type().Key() {
		return nil, fmt.Errorf("incompatible map types, got %T to %T", src, dst)
	}

	return mergeMap(vdst, vsrc).Interface(), nil
}

func caseInsensitiveLookup(m, k reflect.Value) (reflect.Value, bool) {

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Verify the last argument to merge is a map/dict: `{{ printf "%T" $dst }}` to inspect its type.
  2. If the value can be missing, guard with `{{ with }}` or supply a default dict: `merge $src (default dict $dst)`.
  3. Fix the front matter or data file so the key is a map (key: value pairs) rather than a list (- items).
  4. If you meant to combine slices, use `append` or `union` instead of `merge`.

Example fix

<!-- before -->
{{ $merged := merge .Params.extra .Site.Params.tags }}  <!-- tags is a list -->
<!-- after -->
{{ $merged := merge .Params.extra (default dict .Site.Params.settings) }}
Defensive patterns

Strategy: type-guard

Validate before calling

{{ if reflect.IsMap $dst }}{{ $merged := merge $dst $src }}{{ end }}

Type guard

{{/* Hugo template guard */}}{{ $isMap := reflect.IsMap $dst }}

Try / catch

{{ with try (merge $dst $src) }}{{ with .Err }}{{ warnf "merge failed: %s" . }}{{ else }}{{ .Value }}{{ end }}{{ end }}

Prevention

When it happens

Trigger: Calling `{{ merge $a $b }}` in a template where the last argument (the destination) is a slice, string, number, nil, or a Page rather than a map/dict. Also triggered mid-chain when an intermediate merge result isn't a map. Common calls: `merge .Params site.Params`, `merge $opts (dict ...)`.

Common situations: Passing `.Params.something` that is a YAML list instead of a map; front matter key defined as a list in one page and a map in another; using `merge` on `.Site.Menus` entries or slices returned by `where`; a data file (data/*.yaml) structured as an array instead of an object.

Related errors


AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31). Data as JSON: /data/errors/134cf20b1d295f3a.json. Report an issue: GitHub ↗.