gohugoio/hugo · error
cannot unmarshal CSV into %T: header row contains duplicate…
Error message
cannot unmarshal CSV into %T: header row contains duplicate field names
What it means
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.
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.
Example fix
# before: data.csv (duplicate header) name,name,value foo,bar,1 # after: data.csv name,label,value foo,bar,1
Defensive patterns
Strategy: validation
Validate before calling
// Detect duplicate CSV headers for map mode.
func csvHeadersUnique(records [][]string) bool {
seen := map[string]bool{}
for _, h := range records[0] {
if seen[h] { return false }
seen[h] = true
}
return true
} Try / catch
if err := dec.UnmarshalTo(data, metadecoders.CSV, &v); err != nil {
if strings.Contains(err.Error(), "duplicate field names") {
// rename duplicate headers upstream
}
return err
} Prevention
- Make every CSV header unique for map mode.
- Use slice mode for positional multi-value columns.
- Re-export from source with distinct headers.
When it happens
Trigger: 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.
Common situations: Exporting from a spreadsheet with merged/duplicated headers; a data feed that repeats a column; accidental duplication when editing a CSV template.
Related errors
- cannot unmarshal CSV into %T: expected at least a header…
- cannot unmarshal CSV into %T
- cannot unmarshal CSV into %T: invalid targetType: expected…
- failed to unmarshal XML
- invalid targetType: expected either slice or map, received
AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09).
Data as JSON: /api/errors/71b31b437bc5b902.
Report an issue: GitHub.
Appendix: source
Thrown at parser/metadecoders/decoder.go:357
r.LazyQuotes = d.LazyQuotes
records, err := r.ReadAll()
if err != nil {
return err
}
switch vv := v.(type) {
case *any:
switch d.TargetType {
case "map":
if len(records) < 2 {
return fmt.Errorf("cannot unmarshal CSV into %T: expected at least a header row and one data row", v)
}
seen := make(map[string]bool, len(records[0]))
for _, fieldName := range records[0] {
if seen[fieldName] {
return fmt.Errorf("cannot unmarshal CSV into %T: header row contains duplicate field names", v)
}
seen[fieldName] = true
}
sm := make([]map[string]string, len(records)-1)
for i, record := range records[1:] {
m := make(map[string]string, len(records[0]))
for j, col := range record {
m[records[0][j]] = col
}
sm[i] = m
}
*vv = sm
case "slice":
*vv = records
default:
return fmt.Errorf("cannot unmarshal CSV into %T: invalid targetType: expected either slice or map, received %s", v, d.TargetType)
}View on GitHub (pinned to 52c9bd7908)