getgrav/grav · error · RuntimeException
Failed to save file '%s': %s
Error message
Failed to save file '%s': %s
What it means
DataFile::save() accepts either an array (which the configured formatter encodes) or a raw string. A string is trusted only after a round-trip check: the formatter decodes it, and any RuntimeException (invalid syntax for the target format) aborts the save with this message (path + formatter reason). The existing file is left untouched because encoding happens before any write.
Source
Thrown at system/src/Grav/Framework/File/DataFile.php:69
return $this->formatter->decode($raw);
} catch (RuntimeException $e) {
throw new RuntimeException(sprintf("Failed to load file '%s': %s", $this->getFilePath(), $e->getMessage()), $e->getCode(), $e);
}
}
/**
* {@inheritdoc}
* @see FileInterface::save()
*/
public function save($data): void
{
if (is_string($data)) {
// Make sure that the string is valid data.
try {
$this->formatter->decode($data);
} catch (RuntimeException $e) {
throw new RuntimeException(sprintf("Failed to save file '%s': %s", $this->getFilePath(), $e->getMessage()), $e->getCode(), $e);
}
$encoded = $data;
} else {
$encoded = $this->formatter->encode($data);
}
parent::save($encoded);
}
}
View on GitHub (pinned to 6040efed04)
Solutions
- Pass arrays and let the formatter encode them — the intended API contract.
- If a raw string must be saved, validate it first: json_decode($s) with json_last_error(), or the formatter's decode() in a try/catch.
- Fix the syntax error named in the message tail.
- Strip BOM and surrounding whitespace from external strings before passing them in.
Example fix
// before
$file->save('{ "a": 1,, }');
// after (preferred: arrays)
$file->save(['a' => 1]);
// or validated raw string
json_decode($raw);
if (json_last_error() !== JSON_ERROR_NONE) { throw new InvalidArgumentException('bad payload'); }
$file->save($raw); Defensive patterns
Strategy: validation
Validate before calling
if (is_string($data)) {
// same check DataFile performs: verify the string parses before save()
json_decode($data);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \InvalidArgumentException('payload is not valid JSON');
}
}
$dataFile->save($data); Type guard
function isFormattablePayload(mixed $data): bool
{
return is_array($data) || is_string($data);
} Try / catch
try {
$dataFile->save($data);
} catch (\RuntimeException $e) {
// string failed the round-trip check; file untouched — fix payload and retry
$log->error($e->getMessage());
} Prevention
- Prefer passing arrays; let the formatter own serialization.
- Validate externally sourced strings with the same format's parser before saving.
- Never assemble JSON/YAML/INI text by concatenation.
When it happens
Trigger: Calling save("not valid { json") on a .json DataFile; an INI or CSV string whose syntax the corresponding formatter rejects; strings built by concatenation or templates that contain stray quotes, BOMs or half-written fragments.
Common situations: Plugins composing JSON/YAML text by hand instead of passing arrays; piping downloaded or user-submitted strings into save(); mixing encodings that make the formatter's parser fail.
Related errors
- Failed to save file {filepath}
- Bad Data
- Failed to load file '%s': %s
- Cannot save data, string required
- Encoding JSON failed: {json_last_error_msg}
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/8d68be2dc0d9b156.
Report an issue: GitHub.