gohugoio/hugo · error
incompatible map types, got %T to %T
Error message
incompatible map types, got %T to %T
What it means
Thrown by Namespace.merge when the source map's key type does not equal the destination map's key type (checked via reflect Type().Key() equality). Merge requires homogeneous key types so SetMapIndex is valid; e.g. merging map[int]V into map[string]V is rejected. Both source (%T) and destination (%T) types are named.
Solutions
- Normalize key types before merging — convert one map's keys to match the other.
- Use `dict` consistently so all maps are map[string]any.
- Merge maps of the same declared type end-to-end.
- If types genuinely differ, merge manually in a loop with explicit key conversion.
Example fix
// before
{{ merge $intKeyedMap $stringKeyedMap }}
// after
{{ merge (dict "a" 1) $stringKeyedMap }} Defensive patterns
Strategy: validation
Validate before calling
func mergeKeyTypesMatch(src, dst any) error {
vs, vd := reflect.ValueOf(src), reflect.ValueOf(dst)
if vs.Kind() != reflect.Map || vd.Kind() != reflect.Map {
return errors.New("both must be maps")
}
if vs.Type().Key() != vd.Type().Key() {
return fmt.Errorf("key types differ: %s vs %s", vs.Type().Key(), vd.Type().Key())
}
return nil
} Type guard
func sameMapKeyType(a, b any) bool {
va, vb := reflect.ValueOf(a), reflect.ValueOf(b)
if va.Kind() != reflect.Map || vb.Kind() != reflect.Map { return false }
return va.Type().Key() == vb.Type().Key()
} Prevention
- Use map[string]any (dict) consistently so key types align.
- Normalize key types before merging maps from different sources.
- Avoid mixing int-keyed and string-keyed maps in the same Merge.
- Merge manually with explicit conversion if types genuinely differ.
When it happens
Trigger: Calling `{{ merge $intKeyedMap $stringKeyedMap }}`, or mixing a `dict` (map[string]any) with a map[int]any built elsewhere. The vsrc.Type().Key() != vdst.Type().Key() check fires.
Common situations: Combining maps constructed in different places (one from JSON with string keys, one built in Go with int keys), or merging Hugo Params (string-keyed) with a custom int-keyed lookup. Common during integration of data from multiple sources.
Related errors
- destination must be a map, got %T
- is not assignable to
- source must be a map, got %T
- argument must be a slice
- arguments must be slices or arrays
AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09).
Data as JSON: /api/errors/6329c41db7f1af5e.
Report an issue: GitHub.
Appendix: source
Thrown at tpl/collections/merge.go:65
// 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) {
if m.Type().Key().Kind() != reflect.String || k.Kind() != reflect.String {
// Fall back to direct lookup.
v := m.MapIndex(k)
return v, hreflect.IsTruthfulValue(v)
}
k2 := reflect.New(m.Type().Key()).Elem()
iter := m.MapRange()
for iter.Next() {
k2.SetIterKey(iter)
if strings.EqualFold(k.String(), k2.String()) {View on GitHub (pinned to 52c9bd7908)