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

  1. Normalize key types before merging — convert one map's keys to match the other.
  2. Use `dict` consistently so all maps are map[string]any.
  3. Merge maps of the same declared type end-to-end.
  4. 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

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


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)