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
- Verify the last argument to merge is a map/dict: `{{ printf "%T" $dst }}` to inspect its type.
- If the value can be missing, guard with `{{ with }}` or supply a default dict: `merge $src (default dict $dst)`.
- Fix the front matter or data file so the key is a map (key: value pairs) rather than a list (- items).
- 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
- Verify the destination is a map (e.g. built with dict or from .Params) before calling merge
- Beware of variables that may be nil or a slice when a data file is missing or malformed
- Use reflect.IsMap to branch instead of assuming the shape of front matter values
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
- source must be a map, got %T
- arguments to complement must be slices or arrays
- %s isn't a key of map type %s
- %s is neither a struct field, a method nor a map element of
- type %T not supported in Resource transformations
AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31).
Data as JSON: /data/errors/134cf20b1d295f3a.json.
Report an issue: GitHub ↗.