{"record":{"id":"2d947771a632824a","repo":"getgrav/grav","slug":"invalid-storage-key-s","errorCode":null,"errorMessage":"Invalid storage key: \"%s\"","messagePattern":"Invalid storage key: \"(.+?)\"","errorType":"validation","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"system/src/Grav/Framework/Flex/Storage/AbstractFilesystemStorage.php","lineNumber":255,"sourceCode":"\n        // Key must not start with a dot (hidden files)\n        if (str_starts_with($key, '.')) {\n            return false;\n        }\n\n        return true;\n    }\n\n    /**\n     * Validates a key and throws an exception if invalid.\n     *\n     * @param string $key\n     * @throws \\InvalidArgumentException\n     */\n    public function assertValidKey(string $key): void\n    {\n        if (!$this->validateKey($key)) {\n            throw new \\InvalidArgumentException(sprintf('Invalid storage key: \"%s\"', $key));\n        }\n    }\n}\n","sourceCodeStart":237,"sourceCodeEnd":259,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Framework/Flex/Storage/AbstractFilesystemStorage.php#L237-L259","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n$directory->save(['title' => 'Q1/Q2: Report'], 'Q1/Q2: Report'); // throws Invalid storage key\n\n// after\n$key = preg_replace('/[^\\\\w\\-.]+/u', '-', $key) ?? '';\n$key = trim($key, '-') ?: uniqid('object-');\n$directory->save(['title' => 'Q1/Q2: Report'], $key);","handlingStrategy":"validation","validationCode":"// Mirror AbstractFilesystemStorage::validateKey() before saving\nfunction isValidStorageKey(string $key): bool\n{\n    return $key !== '' && preg_match('/^[^\\\\/?*:;{}\\\\\\\\\\\\n]+$/u', $key) === 1;\n}\n\n$key = $data['key'] ?? null;\nif (!\\is_string($key) || !isValidStorageKey($key)) {\n    $key = preg_replace('/[^\\\\w\\-.]+/u', '-', (string) $key) ?? '';\n    $key = trim($key, '-') ?: uniqid('object-');\n}\n$directory->save($data, $key);","typeGuard":"function isValidFlexStorageKey(mixed $key): bool\n{\n    return \\is_string($key)\n        && $key !== ''\n        && preg_match('/^[^\\\\/?*:;{}\\\\\\\\\\\\n]+$/u', $key) === 1;\n}","tryCatchPattern":"try {\n    $directory->save($data, $key);\n} catch (\\InvalidArgumentException $e) {\n    if (str_starts_with($e->getMessage(), 'Invalid storage key')) {\n        $key = preg_replace('/[^\\\\w\\-.]+/u', '-', $key);\n        $directory->save($data, $key);\n    } else {\n        throw $e;\n    }\n}","preventionTips":["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."],"tags":["flex","storage","validation","filesystem","grav"],"backgroundTag":"invalid-filename-characters","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}