{"record":{"id":"71b31b437bc5b902","repo":"gohugoio/hugo","slug":"cannot-unmarshal-csv-into-t-header-row-contains","errorCode":null,"errorMessage":"cannot unmarshal CSV into %T: header row contains duplicate field names","messagePattern":"cannot unmarshal CSV into %T: header row contains duplicate field names","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"parser/metadecoders/decoder.go","lineNumber":357,"sourceCode":"\tr.LazyQuotes = d.LazyQuotes\n\n\trecords, err := r.ReadAll()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch vv := v.(type) {\n\tcase *any:\n\t\tswitch d.TargetType {\n\t\tcase \"map\":\n\t\t\tif len(records) < 2 {\n\t\t\t\treturn fmt.Errorf(\"cannot unmarshal CSV into %T: expected at least a header row and one data row\", v)\n\t\t\t}\n\n\t\t\tseen := make(map[string]bool, len(records[0]))\n\t\t\tfor _, fieldName := range records[0] {\n\t\t\t\tif seen[fieldName] {\n\t\t\t\t\treturn fmt.Errorf(\"cannot unmarshal CSV into %T: header row contains duplicate field names\", v)\n\t\t\t\t}\n\t\t\t\tseen[fieldName] = true\n\t\t\t}\n\n\t\t\tsm := make([]map[string]string, len(records)-1)\n\t\t\tfor i, record := range records[1:] {\n\t\t\t\tm := make(map[string]string, len(records[0]))\n\t\t\t\tfor j, col := range record {\n\t\t\t\t\tm[records[0][j]] = col\n\t\t\t\t}\n\t\t\t\tsm[i] = m\n\t\t\t}\n\t\t\t*vv = sm\n\t\tcase \"slice\":\n\t\t\t*vv = records\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"cannot unmarshal CSV into %T: invalid targetType: expected either slice or map, received %s\", v, d.TargetType)\n\t\t}","sourceCodeStart":339,"sourceCodeEnd":375,"githubUrl":"https://github.com/gohugoio/hugo/blob/52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2/parser/metadecoders/decoder.go#L339-L375","documentation":"Thrown by Decoder.unmarshalCSV (parser/metadecoders/decoder.go:357) when TargetType is \"map\" and the CSV header row (first record) contains duplicate field names. Map-mode CSV turns each row into a map keyed by header names; duplicate names would collide/overwrite, so Hugo rejects it upfront. The %T reflects the target.","triggerScenarios":"A CSV whose first row has repeated column headers (e.g. name,name,value); an export tool that duplicates column titles; a hand-authored CSV with a copy-paste header mistake.","commonSituations":"Exporting from a spreadsheet with merged/duplicated headers; a data feed that repeats a column; accidental duplication when editing a CSV template.","solutions":["Make every header in the first row unique.","If duplicate columns are intentional (e.g. multi-value), switch to TargetType=\"slice\" and index columns positionally.","Re-export the CSV from the source ensuring distinct column names."],"exampleFix":"# before: data.csv (duplicate header)\nname,name,value\nfoo,bar,1\n\n# after: data.csv\nname,label,value\nfoo,bar,1","handlingStrategy":"validation","validationCode":"// Detect duplicate CSV headers for map mode.\nfunc csvHeadersUnique(records [][]string) bool {\n    seen := map[string]bool{}\n    for _, h := range records[0] {\n        if seen[h] { return false }\n        seen[h] = true\n    }\n    return true\n}","typeGuard":null,"tryCatchPattern":"if err := dec.UnmarshalTo(data, metadecoders.CSV, &v); err != nil {\n    if strings.Contains(err.Error(), \"duplicate field names\") {\n        // rename duplicate headers upstream\n    }\n    return err\n}","preventionTips":["Make every CSV header unique for map mode.","Use slice mode for positional multi-value columns.","Re-export from source with distinct headers."],"tags":["csv","metadecoders","data","hugo"],"backgroundTag":null,"analyzedSha":"52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2","analyzedAt":"2026-08-09T21:49:36.660Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}