getgrav/grav · error · RuntimeException
Cannot save data, string required
Error message
Cannot save data, string required
What it means
Grav\Framework\File\File is the raw, format-agnostic file handler: its save() demands a PHP string and throws for any other type before delegating to AbstractFile::save(). Structured data (arrays/objects) belongs to DataFile or a formatter-aware class, which encodes automatically. This is a strict API-contract error — nothing was written.
Source
Thrown at system/src/Grav/Framework/File/File.php:30
namespace Grav\Framework\File;
use RuntimeException;
use function is_string;
/**
* Class File
* @package Grav\Framework\File
*/
class File extends AbstractFile
{
/**
* {@inheritdoc}
* @see FileInterface::save()
*/
public function save($data): void
{
if (!is_string($data)) {
throw new RuntimeException('Cannot save data, string required');
}
parent::save($data);
}
}
View on GitHub (pinned to 6040efed04)
Solutions
- Cast the payload: $file->save((string) $data).
- Switch to Grav\Framework\File\DataFile (or the filesystem/formatter factory) when saving structured data.
- Add a type check at the call site (is_string($data)) or a native string type hint in your own wrapper.
Example fix
// before $file = new \Grav\Framework\File\File($path); $file->save(['foo' => 'bar']); // RuntimeException: Cannot save data, string required // after (structured data -> DataFile via formatter-aware factory) $file = $filesystem->file($path); // DataFile bound to the right formatter $file->save(['foo' => 'bar']); // or raw string $file->save((string) json_encode(['foo' => 'bar']));
Defensive patterns
Strategy: type-guard
Validate before calling
if (!is_string($data)) {
$data = (string) $data; // or switch to a DataFile for structured data
}
$file->save($data); Type guard
function isRawStringPayload(mixed $data): bool
{
return is_string($data);
} Try / catch
try {
$file->save($data);
} catch (\RuntimeException $e) {
if (str_contains($e->getMessage(), 'string required')) {
$file->save((string) $data);
}
} Prevention
- Choose File (raw bytes) vs DataFile (structured) deliberately at design time.
- Declare string parameter types in your own wrappers so misuse fails at the boundary.
- Cast scalars explicitly before saving.
When it happens
Trigger: Calling save(['key' => 'value']), save(123) or save($object) on a File instance: using File where DataFile was intended; passing an un-cast scalar from config; refactors that changed the payload from string to array without changing the class.
Common situations: Copy-pasting DataFile examples against a File object; switching persistence from serialized strings to structured arrays; saving computed ints/floats/bools without an explicit cast.
Related errors
- Failed to save file {filepath}
- Failed to save file '%s': %s
- Bad data
- Cannot unserialize Block: %s
- Unsupported version %s
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/352909f6d7c37fb6.
Report an issue: GitHub.