{"record":{"id":"b13a8f025ebea695","repo":"gohugoio/hugo","slug":"source-must-be-a-map-got-t","errorCode":null,"errorMessage":"source must be a map, got %T","messagePattern":"source must be a map, got %T","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tpl/collections/merge.go","lineNumber":61,"sourceCode":"\t}\n\n\treturn result, nil\n}\n\n// merge creates a copy of dst and merges src into it.\nfunc (ns *Namespace) merge(src, dst any) (any, error) {\n\tvdst, vsrc := reflect.ValueOf(dst), reflect.ValueOf(src)\n\n\tif vdst.Kind() != reflect.Map {\n\t\treturn nil, fmt.Errorf(\"destination must be a map, got %T\", dst)\n\t}\n\n\tif !hreflect.IsTruthfulValue(vsrc) {\n\t\treturn dst, nil\n\t}\n\n\tif vsrc.Kind() != reflect.Map {\n\t\treturn nil, fmt.Errorf(\"source must be a map, got %T\", src)\n\t}\n\n\tif vsrc.Type().Key() != vdst.Type().Key() {\n\t\treturn nil, fmt.Errorf(\"incompatible map types, got %T to %T\", src, dst)\n\t}\n\n\treturn mergeMap(vdst, vsrc).Interface(), nil\n}\n\nfunc caseInsensitiveLookup(m, k reflect.Value) (reflect.Value, bool) {\n\tif m.Type().Key().Kind() != reflect.String || k.Kind() != reflect.String {\n\t\t// Fall back to direct lookup.\n\t\tv := m.MapIndex(k)\n\t\treturn v, hreflect.IsTruthfulValue(v)\n\t}\n\n\tk2 := reflect.New(m.Type().Key()).Elem()\n","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/gohugoio/hugo/blob/52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2/tpl/collections/merge.go#L43-L79","documentation":"Thrown by Namespace.merge when a source parameter (any non-final param to Merge) is reflect-truthy but not reflect.Map. Sources contribute keys to the destination; a non-map source cannot be merged. The %T verb names the offending source type. Note a nil/zero source is silently skipped (IsTruthfulValue guard), so this fires only for truthy non-maps.","triggerScenarios":"Calling `{{ merge $notAMap $dstMap }}` where $notAMap is a truthy slice, struct, or scalar. Because Merge iterates sources in reverse, each preceding param is validated here.","commonSituations":"Author passes a list or page object intending its fields to merge, or a partial receives heterogeneous args. A nil source is tolerated, but any truthy non-map triggers this.","solutions":["Pass only maps as sources: `{{ merge $overrides $defaults $dst }}`.","Convert a struct to a map before merging (e.g. extract needed fields into a dict).","Drop sources that are unexpectedly non-map.","Validate each source is a map before the call."],"exampleFix":"// before\n{{ merge $pageList $dstMap }}\n// after\n{{ merge (dict \"key\" \"val\") $dstMap }}","handlingStrategy":"type-guard","validationCode":"func mergeSourcesAreMaps(params ...any) error {\n    for i := 0; i < len(params)-1; i++ {\n        if !hreflect.IsTruthfulValue(reflect.ValueOf(params[i])) { continue }\n        if reflect.ValueOf(params[i]).Kind() != reflect.Map {\n            return fmt.Errorf(\"merge source must be map, got %T\", params[i])\n        }\n    }\n    return nil\n}","typeGuard":"func isMap(v any) bool {\n    if v == nil { return false }\n    return reflect.ValueOf(v).Kind() == reflect.Map\n}","tryCatchPattern":null,"preventionTips":["Pass only maps as Merge sources.","Convert structs/lists to dicts before merging.","Remember truthy non-maps are rejected (nil sources are tolerated).","Validate each source is a map at the call site."],"tags":["go","hugo","template","collections","merge","type-error"],"backgroundTag":null,"analyzedSha":"52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2","analyzedAt":"2026-08-09T21:49:36.660Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}