getgrav/grav · error · InvalidArgumentException
Cache key "%s" contains reserved characters {}()/\@:
Error message
Cache key "%s" contains reserved characters {}()/\@: What it means
PSR-16 reserves the characters {}()/\@: in cache keys because several backends use them as delimiters or namespace separators. Grav's validateKey() checks with strpbrk() and throws InvalidArgumentException naming the offending key when any reserved character is present.
Source
Thrown at system/src/Grav/Framework/Cache/CacheTrait.php:322
{
if (!is_string($key)) {
throw new InvalidArgumentException(
sprintf(
'Cache key must be string, "%s" given',
get_debug_type($key)
)
);
}
if (!isset($key[0])) {
throw new InvalidArgumentException('Cache key length must be greater than zero');
}
if (strlen($key) > 64) {
throw new InvalidArgumentException(
sprintf('Cache key length must be less than 65 characters, key had %d characters', strlen($key))
);
}
if (strpbrk($key, '{}()/\@:') !== false) {
throw new InvalidArgumentException(
sprintf('Cache key "%s" contains reserved characters {}()/\@:', $key)
);
}
}
/**
* @param array $keys
* @return void
* @throws InvalidArgumentException
*/
protected function validateKeys(iterable $keys): void
{
if (!$this->validation) {
return;
}
foreach ($keys as $key) {
$this->validateKey($key);View on GitHub (pinned to 6040efed04)
Solutions
- Hash or encode the natural key: $key = md5($uri) or $key = urlencode($path) — but prefer hashing since urlencode can still exceed 64 chars.
- Replace delimiters when a readable key matters: $key = strtr($path, ['/' => '-', ':' => '_']).
- Wrap key generation once (single KeyBuilder/helper) so no call site hand-crafts keys from URLs or paths.
Example fix
// before
$cache->get('page:' . $uri); // 'page:https://...' -> reserved : / chars -> throws
// after
$cache->get('page-' . md5($uri)); // reserved-free Defensive patterns
Strategy: validation
Validate before calling
// make any natural key safe before use
$safeKey = preg_replace('/[{}()\/\\@:]/', '-', $naturalKey) ?? '';
if (strlen($safeKey) > 64) {
$safeKey = 'k-' . md5($naturalKey);
}
$cache->get($safeKey !== '' ? $safeKey : 'k-default'); Prevention
- Never use raw URLs, file paths, or emails as cache keys — hash or strtr() them first.
- Avoid sprintf templates with literal braces ('{id}') in key strings.
- One shared key-sanitizing helper beats per-call-site improvisation.
When it happens
Trigger: Using a URL as a key ('https://example.com/page' contains '/', ':' and '//'); using file paths ('user/pages/blog.md' contains '/'); keys templated with sprintf braces ('page_{id}'); email addresses or user@host identifiers ('@'); Windows-style paths ('\').
Common situations: Keying page-fragment caches by raw REQUEST_URI; caching by filesystem path in custom plugins; interpolating templates with literal braces in the key; multi-site setups embedding 'site(a)' style markers in keys.
Related errors
- Cache key must be string, "%s" given
- Cache key length must be greater than zero
- Cache key length must be less than 65 characters, key had %d
- The class '%s' does not implement the '%s' interface
- Cache keys must be array or Traversable, "%s" given
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/5a14dadea07c712d.
Report an issue: GitHub.