gohugoio/hugo · error

destination must be a map, got %T

Error message

destination must be a map, got %T

What it means

Thrown by Namespace.merge (helper for Merge) when the destination map — the LAST parameter to Merge — is not reflect.Map. Merge copies the destination and merges preceding sources into it; the destination must be a map to receive keys. The %T verb names the offending destination type.

Solutions

  1. Ensure the last argument is a map: `{{ merge $defaults $page.Params }}` (destination last).
  2. Construct a fresh map destination: `{{ merge $src (dict "k" "v") }}`.
  3. Re-read Merge docs: result is a copy of the final param with earlier params merged in reverse.
  4. Log the type of each argument to verify ordering.

Example fix

// before
{{ merge $page.Params $defaults }}
// after
{{ merge $defaults $page.Params }}
Defensive patterns

Strategy: type-guard

Validate before calling

func mergeDstIsMap(params ...any) error {
    if len(params) < 2 { return errors.New("need >=2 params") }
    dst := params[len(params)-1]
    if reflect.ValueOf(dst).Kind() != reflect.Map {
        return fmt.Errorf("merge destination must be map, got %T", dst)
    }
    return nil
}

Type guard

func isMap(v any) bool {
    if v == nil { return false }
    return reflect.ValueOf(v).Kind() == reflect.Map
}

Prevention

When it happens

Trigger: Calling `{{ merge $src $notAMap }}` where $notAMap is a slice, struct, string, or scalar. Note Merge treats the final param as destination and iterates backward, so this fires when params[len(params)-1] is non-map.

Common situations: Argument order confusion (author assumes first arg is destination), passing a page or resource struct as the merge target, or a variable that resolved to nil/non-map after data loading.

Related errors


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

Appendix: 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 52c9bd7908)