getgrav/grav · error · RuntimeException
Badly formatted CSV line: {line}
Error message
Badly formatted CSV line: {line} What it means
CsvFormatter::decode() parses each line with str_getcsv() and merges it with the header via array_combine($header, $csv_line) inside a try/catch; any Exception thrown while processing a line is rethrown as 'Badly formatted CSV line: <line>'. The dominant cause is a field-count mismatch between a row and the header — one side of array_combine longer than the other — typically from unquoted delimiters, missing trailing delimiters, or the wrong delimiter being used.
Source
Thrown at system/src/Grav/Framework/File/Formatter/CsvFormatter.php:124
// Get the data
$list = [];
$line = null;
try {
foreach ($lines as $line) {
if (!empty($line)) {
$csv_line = str_getcsv($line, $delimiter);
if ($null_replace) {
array_walk($csv_line, static function (&$el) use ($null_replace) {
$el = str_replace($null_replace, "\0", $el);
});
}
$list[] = array_combine($header, $csv_line);
}
}
} catch (Exception) {
throw new RuntimeException('Badly formatted CSV line: ' . $line);
}
return $list;
}
/**
* @param array $line
* @param string $delimiter
* @return string
*/
protected function encodeLine(array $line, string $delimiter): string
{
foreach ($line as $key => &$value) {
// Oops, we need to convert the line to a string.
if (!is_scalar($value)) {
if (is_array($value) || $value instanceof JsonSerializable || $value instanceof stdClass) {
$value = json_encode($value);
} elseif (is_object($value)) {View on GitHub (pinned to 6040efed04)
Solutions
- Inspect the line named in the message and balance its field count against the header.
- Match the delimiter: pass it explicitly decode($data, ';') or configure the CsvFormatter — a systematic wrong delimiter produces this error on every row.
- Re-export with proper quoting so values containing the delimiter are enclosed in quotes.
- Pre-validate rows before decoding: count(str_getcsv($line, $del)) must equal count($header); skip or report offenders.
Example fix
// before $list = $formatter->decode($raw); // Badly formatted CSV line: 1;2;3;4 // after: pass the actual delimiter $list = $formatter->decode($raw, ';'); // or guard rows against the header width $header = str_getcsv($headerLine, $del); $clean = array_filter($lines, fn($l) => count(str_getcsv($l, $del)) === count($header));
Defensive patterns
Strategy: validation
Validate before calling
// Verify every row's width matches the header before decode()
$lines = preg_split('/\r\n|\r|\n/', trim($data));
$header = str_getcsv((string) array_shift($lines), $del);
foreach ($lines as $l) {
if ($l !== '' && count(str_getcsv($l, $del)) !== count($header)) {
// report/skip this row now instead of failing the whole decode
}
} Try / catch
try {
$list = $formatter->decode($data);
} catch (\RuntimeException $e) {
if (preg_match('/Badly formatted CSV line/', $e->getMessage())) {
// row-level data error: log and continue with remaining rows if acceptable
}
} Prevention
- Pin the delimiter explicitly for locale-sensitive exports (comma vs semicolon).
- Require quoting of values containing the delimiter when generating CSV.
- Validate row widths on ingest and reject offending rows with context.
When it happens
Trigger: A data row with more fields than the header (unquoted delimiter inside a value) or fewer (missing trailing comma); a delimiter mismatch — the file is semicolon/tab separated while the formatter uses commas, so counts never line up; rows of a different width appended from another source.
Common situations: Spreadsheet exports with unquoted commas in text fields; concatenating CSVs that share a schema prefix but drift later; European locale exports (semicolon delimiter) read with the default comma config; hand-edited rows.
Related errors
- CSV header missing
- Failed to load file '%s': %s
- Decoding CSV failed
- Decoding INI failed
- Decoding JSON failed: {json_last_error_msg}
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/d10b4b1ba600dfde.
Report an issue: GitHub.