getgrav/grav · error · RuntimeException
CSV header missing
Error message
CSV header missing
What it means
CsvFormatter::decode() treats the first line as a mandatory header row: after splitting, array_shift removes it and throws when it is falsy. Concretely the payload's first line is empty — the data is an empty string, contains only whitespace/newlines, or begins with blank line(s) — so there are no field names to map columns against.
Source
Thrown at system/src/Grav/Framework/File/Formatter/CsvFormatter.php:98
/**
* @param string $data
* @param string|null $delimiter
* @return array
* @see FileFormatterInterface::decode()
*/
public function decode($data, $delimiter = null): array
{
$delimiter ??= $this->getDelimiter();
$lines = preg_split('/\r\n|\r|\n/', $data);
if ($lines === false) {
throw new RuntimeException('Decoding CSV failed');
}
// Get the field names
$headerStr = array_shift($lines);
if (!$headerStr) {
throw new RuntimeException('CSV header missing');
}
$header = str_getcsv($headerStr, $delimiter);
// Allow for replacing a null string with null/empty value
$null_replace = $this->getConfig('null');
// 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);View on GitHub (pinned to 6040efed04)
Solutions
- Guard empty input before decoding: if (trim($data) === '') treat as empty dataset ([] or defaults), not an error.
- Fix the source to emit a header row (re-export with headers enabled).
- Strip leading blank lines when they are expected: $data = ltrim($data, "\r\n").
- If the schema is known, prepend the header yourself: $data = implode(',', $fields) . "\n" . $data;
Example fix
// before $list = $formatter->decode($raw); // after $raw = is_string($raw) ? ltrim($raw, "\r\n") : ''; $list = ($raw === '') ? [] : $formatter->decode($raw);
Defensive patterns
Strategy: validation
Validate before calling
if (!is_string($data) || trim($data) === '') {
// no header possible: treat as empty dataset instead of calling decode()
return [];
} Try / catch
try {
$list = $formatter->decode($data);
} catch (\RuntimeException $e) {
if (str_contains($e->getMessage(), 'CSV header missing')) {
$list = []; // empty input is not an error for the caller
} else {
throw $e;
}
} Prevention
- Validate uploads/imports are non-empty before parsing.
- Ensure export tools emit a header row.
- Strip leading blank lines from inbound CSV at the boundary.
When it happens
Trigger: decode(''), decode("\n\n1,2"); a CSV export written without its header row; a file truncated so the first line is blank; uploads that are empty before validation.
Common situations: Export tools configured to omit headers; plugins feeding user uploads directly into the formatter; empty seed files shipped with a plugin; files saved with a stray leading newline.
Related errors
- Badly formatted CSV line: {line}
- 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/b0ff60f5a73e415c.
Report an issue: GitHub.