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

  1. Sanitize the key before saving: strip or transliterate the forbidden characters / ? * : ; { } \\ and newlines (e.g. preg_replace('/[^\\w\-. ]+/u', '-', $key)).
  2. If slugs come from user input, use Grav's grav-common sanitize functions or a standard slugifier to produce filesystem-safe keys.
  3. If you genuinely need such ids, store them in a meta/index field and use a safe surrogate key as the storage key.
  4. 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

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


AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17). Data as JSON: /api/errors/2d947771a632824a. Report an issue: GitHub.