getgrav/grav · error · InvalidArgumentException
Invalid storage key: "%s"
Error message
Invalid storage key: "%s"
What it means
Filesystem Flex storage validates every storage key with validateKey(), which rejects keys containing any of the characters '/', '?', '*', ':', ';', '{', '}', '\\' or a newline (regex '/^[^\\/?*:;{}\\\\\\n]+$/u'). assertValidKey() throws InvalidArgumentException with the offending key when validation fails. The restriction exists because the key becomes (part of) a filename on disk, so unsafe characters would break path handling or be unportable across filesystems.
Source
Thrown at system/src/Grav/Framework/Flex/Storage/AbstractFilesystemStorage.php:255
// Key must not start with a dot (hidden files)
if (str_starts_with($key, '.')) {
return false;
}
return true;
}
/**
* Validates a key and throws an exception if invalid.
*
* @param string $key
* @throws \InvalidArgumentException
*/
public function assertValidKey(string $key): void
{
if (!$this->validateKey($key)) {
throw new \InvalidArgumentException(sprintf('Invalid storage key: "%s"', $key));
}
}
}
View on GitHub (pinned to 6040efed04)
Solutions
- Sanitize the key before saving: strip or transliterate the forbidden characters / ? * : ; { } \\ and newlines (e.g. preg_replace('/[^\\w\-. ]+/u', '-', $key)).
- If slugs come from user input, use Grav's grav-common sanitize functions or a standard slugifier to produce filesystem-safe keys.
- If you genuinely need such ids, store them in a meta/index field and use a safe surrogate key as the storage key.
- When importing, validate every id against the same regex first and report offending rows instead of letting storage throw.
Example fix
// before
$directory->save(['title' => 'Q1/Q2: Report'], 'Q1/Q2: Report'); // throws Invalid storage key
// after
$key = preg_replace('/[^\\w\-.]+/u', '-', $key) ?? '';
$key = trim($key, '-') ?: uniqid('object-');
$directory->save(['title' => 'Q1/Q2: Report'], $key); Defensive patterns
Strategy: validation
Validate before calling
// Mirror AbstractFilesystemStorage::validateKey() before saving
function isValidStorageKey(string $key): bool
{
return $key !== '' && preg_match('/^[^\\/?*:;{}\\\\\\n]+$/u', $key) === 1;
}
$key = $data['key'] ?? null;
if (!\is_string($key) || !isValidStorageKey($key)) {
$key = preg_replace('/[^\\w\-.]+/u', '-', (string) $key) ?? '';
$key = trim($key, '-') ?: uniqid('object-');
}
$directory->save($data, $key); Type guard
function isValidFlexStorageKey(mixed $key): bool
{
return \is_string($key)
&& $key !== ''
&& preg_match('/^[^\\/?*:;{}\\\\\\n]+$/u', $key) === 1;
} Try / catch
try {
$directory->save($data, $key);
} catch (\InvalidArgumentException $e) {
if (str_starts_with($e->getMessage(), 'Invalid storage key')) {
$key = preg_replace('/[^\\w\-.]+/u', '-', $key);
$directory->save($data, $key);
} else {
throw $e;
}
} Prevention
- Never pass raw user input (titles, emails, URLs) as a Flex storage key; slugify first.
- Validate imported ids against the forbidden-character set and reject bad rows with an error report instead of saving.
- Add a form validation rule so keys are restricted to [A-Za-z0-9._-] at the point of entry.
When it happens
Trigger: Calling $directory->save() / createObject / updateObject with a key like 'foo/bar', 'user:1', 'a{b}', 'key*', or a key containing a newline; Flex forms or API endpoints accepting user input as the storage key without sanitizing it; keys generated from titles or emails that keep reserved characters.
Common situations: A frontend/user-generated Flex object form where the slug/key is derived from a raw title containing '/', ':' or '*'; importing data (CSV/json) whose ids contain colons or slashes; Windows-hostile characters used in a key generated on macOS/Linux.
Related errors
- 500
- Unknown extension type
- Bad Data Formatter
- Backup location: {$backup_root} does not exist...
- Invalid backup location: {$backup_root}
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/2d947771a632824a.
Report an issue: GitHub.