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

  1. Inspect the line named in the message and balance its field count against the header.
  2. Match the delimiter: pass it explicitly decode($data, ';') or configure the CsvFormatter — a systematic wrong delimiter produces this error on every row.
  3. Re-export with proper quoting so values containing the delimiter are enclosed in quotes.
  4. 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

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


AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17). Data as JSON: /api/errors/d10b4b1ba600dfde. Report an issue: GitHub.