gohugoio/hugo · error
cannot unmarshal CSV into %T: expected at least a header…
Error message
cannot unmarshal CSV into %T: expected at least a header row and one data row
What it means
Thrown by Decoder.unmarshalCSV (parser/metadecoders/decoder.go:351) when TargetType is "map" but the CSV has fewer than 2 records. Map-mode CSV uses the first row as field-name headers and subsequent rows as data; with zero or one row there is no data to map, so Hugo refuses rather than returning an empty/ambiguous result. The %T reflects the target (typically *interface{}).
Solutions
- Ensure the CSV has at least one header row plus one or more data rows when using map mode.
- If empty data is legitimate, use TargetType="slice" which tolerates fewer rows, or handle the empty case before decoding.
- Add a sentinel/placeholder data row, or skip decoding when the file is empty.
Example fix
# before: data.csv (header only) name,value # after: data.csv name,value foo,1
Defensive patterns
Strategy: validation
Validate before calling
// Ensure CSV has header + at least one row for map mode.
func csvHasMapData(records [][]string) bool {
return len(records) >= 2
} Try / catch
if err := dec.UnmarshalTo(data, metadecoders.CSV, &v); err != nil {
if strings.Contains(err.Error(), "at least a header row") {
// switch to slice mode or skip empty file
}
return err
} Prevention
- Use slice mode when CSV may be header-only/empty.
- Skip decoding for empty files.
- Validate row counts for map-mode CSV.
When it happens
Trigger: Loading a .csv data file with TargetType="map" that contains only a header row or is completely empty; a CSV resource referenced via transform.Unmarshal configured for map output but lacking data rows.
Common situations: An empty CSV export; a header-only template CSV; a feed/resource that occasionally returns no rows; misconfiguring a CSV shortcode/resource with map target type.
Related errors
- cannot unmarshal CSV into %T: header row contains duplicate…
- 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/aeaf222881b021f1.
Report an issue: GitHub.
Appendix: source
Thrown at parser/metadecoders/decoder.go:351
}
func (d Decoder) unmarshalCSV(data []byte, v any) error {
r := csv.NewReader(bytes.NewReader(data))
r.Comma = d.Delimiter
r.Comment = d.Comment
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
}View on GitHub (pinned to 52c9bd7908)