{"record":{"id":"0861414ac49a71de","repo":"getgrav/grav","slug":"configuration-is-missing-keys-s-from-streams-sche","errorCode":null,"errorMessage":"Configuration is missing keys %s from streams.schemes!","messagePattern":"Configuration is missing keys (.+?) from streams\\.schemes!","errorType":"exception","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"critical","filePath":"system/src/Grav/Common/Config/Setup.php","lineNumber":398,"sourceCode":"        return $schemes;\n    }\n\n    /**\n     * @param UniformResourceLocator $locator\n     * @return void\n     * @throws InvalidArgumentException\n     * @throws BadMethodCallException\n     * @throws RuntimeException\n     */\n    protected function check(UniformResourceLocator $locator)\n    {\n        $streams = $this->items['streams']['schemes'] ?? null;\n        if (!is_array($streams)) {\n            throw new InvalidArgumentException('Configuration is missing streams.schemes!');\n        }\n        $diff = array_keys(array_diff_key($this->streams, $streams));\n        if ($diff) {\n            throw new InvalidArgumentException(\n                sprintf('Configuration is missing keys %s from streams.schemes!', implode(', ', $diff))\n            );\n        }\n\n        try {\n            // Strip missing override locations from environment://. Runs even when the env\n            // dir itself does not exist on disk, otherwise the stale prefix lingers and a\n            // later write (e.g. config save under a hostname variant) materializes the dir.\n            $force = $this->get('streams.schemes.environment.force', false);\n            if (!$force) {\n                $prefixes = $this->get('streams.schemes.environment.prefixes.');\n                $update = false;\n                foreach ($prefixes as $i => $prefix) {\n                    if ($locator->isStream($prefix)) {\n                        if ($locator->findResource($prefix, true)) {\n                            break;\n                        }\n                    } elseif (file_exists($prefix)) {","sourceCodeStart":380,"sourceCodeEnd":416,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Common/Config/Setup.php#L380-L416","documentation":"Setup::check() diffs Grav's built-in required stream schemes ($this->streams — user, cache, log, tmp, backup, environment, system, asset, blueprints, config, plugins, etc.) against the merged streams.schemes configuration. Any required key missing from the configuration is collected via array_diff_key and reported in one InvalidArgumentException: 'Configuration is missing keys user, cache, ... from streams.schemes!'. Unlike error [8], the schemes array exists but was replaced rather than merged, dropping built-ins.","triggerScenarios":"A user/config/streams.yaml that defines streams.schemes with only custom schemes (array union at boot does not help if setup loading replaced the structure); an environment-specific config that overrides the whole streams block; a plugin or skeleton shipping a streams.yaml that redefines schemes without including the required built-ins.","commonSituations":"Following an old tutorial that shows a full streams.schemes override; adding a custom stream by copying a complete config block instead of appending one scheme; upgrades that introduce new required schemes (e.g. environment) that a frozen custom block lacks.","solutions":["Read the exception message — it names the exact missing keys; add each one back to your streams.schemes override","Prefer additive config: define only your custom scheme under streams.schemes and let Grav's defaults supply the built-ins","Copy the required scheme definitions from system/src/Grav/Common/Config/Setup.php ($streams property) into your override verbatim","Clear the compiled setup cache after fixing so the change takes effect"],"exampleFix":"# user/config/streams.yaml — before (replaces schemes, drops built-ins)\nstreams:\n  schemes:\n    mydocs: { type: ReadOnlyStream, prefixes: { '': ['user://mydocs'] } }\n\n# after — built-ins restored alongside the custom scheme\nstreams:\n  schemes:\n    user: { type: ReadOnlyStream, force: true, prefixes: { '': ['user://'] } }\n    cache: { type: Stream, force: true, prefixes: { '': ['cache://'], images: ['images'] } }\n    system: { type: ReadOnlyStream, prefixes: { '': ['system://'] } }\n    mydocs: { type: ReadOnlyStream, prefixes: { '': ['user://mydocs'] } }","handlingStrategy":"validation","validationCode":"$required = ['user', 'cache', 'log', 'tmp', 'backup', 'environment', 'system', 'asset', 'blueprints', 'config', 'plugins'];\n$merged = $config->get('streams.schemes', []);\n$missing = array_diff($required, array_keys((array) $merged));\nif ($missing) {\n    throw new RuntimeException('streams.schemes missing: ' . implode(', ', $missing));\n}","typeGuard":null,"tryCatchPattern":"try {\n    $grav['config'];\n} catch (InvalidArgumentException $e) {\n    if (str_contains($e->getMessage(), 'missing keys')) {\n        // the exception names the exact keys — copy them from Setup::$streams defaults into the override\n    }\n}","preventionTips":["Add custom schemes alongside built-ins instead of replacing the schemes map","When a Grav upgrade lands, re-diff your streams.yaml against the new required set (Setup::$streams)","Read the exception text — it enumerates exactly which keys to restore"],"tags":["grav","configuration","streams","bootstrap","missing-keys"],"backgroundTag":"missing-config-key","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}