getgrav/grav · critical · InvalidArgumentException
Configuration is missing keys %s from streams.schemes!
Error message
Configuration is missing keys %s from streams.schemes!
What it means
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.
Source
Thrown at system/src/Grav/Common/Config/Setup.php:398
return $schemes;
}
/**
* @param UniformResourceLocator $locator
* @return void
* @throws InvalidArgumentException
* @throws BadMethodCallException
* @throws RuntimeException
*/
protected function check(UniformResourceLocator $locator)
{
$streams = $this->items['streams']['schemes'] ?? null;
if (!is_array($streams)) {
throw new InvalidArgumentException('Configuration is missing streams.schemes!');
}
$diff = array_keys(array_diff_key($this->streams, $streams));
if ($diff) {
throw new InvalidArgumentException(
sprintf('Configuration is missing keys %s from streams.schemes!', implode(', ', $diff))
);
}
try {
// Strip missing override locations from environment://. Runs even when the env
// dir itself does not exist on disk, otherwise the stale prefix lingers and a
// later write (e.g. config save under a hostname variant) materializes the dir.
$force = $this->get('streams.schemes.environment.force', false);
if (!$force) {
$prefixes = $this->get('streams.schemes.environment.prefixes.');
$update = false;
foreach ($prefixes as $i => $prefix) {
if ($locator->isStream($prefix)) {
if ($locator->findResource($prefix, true)) {
break;
}
} elseif (file_exists($prefix)) {View on GitHub (pinned to 6040efed04)
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
Example fix
# user/config/streams.yaml — before (replaces schemes, drops built-ins)
streams:
schemes:
mydocs: { type: ReadOnlyStream, prefixes: { '': ['user://mydocs'] } }
# after — built-ins restored alongside the custom scheme
streams:
schemes:
user: { type: ReadOnlyStream, force: true, prefixes: { '': ['user://'] } }
cache: { type: Stream, force: true, prefixes: { '': ['cache://'], images: ['images'] } }
system: { type: ReadOnlyStream, prefixes: { '': ['system://'] } }
mydocs: { type: ReadOnlyStream, prefixes: { '': ['user://mydocs'] } } Defensive patterns
Strategy: validation
Validate before calling
$required = ['user', 'cache', 'log', 'tmp', 'backup', 'environment', 'system', 'asset', 'blueprints', 'config', 'plugins'];
$merged = $config->get('streams.schemes', []);
$missing = array_diff($required, array_keys((array) $merged));
if ($missing) {
throw new RuntimeException('streams.schemes missing: ' . implode(', ', $missing));
} Try / catch
try {
$grav['config'];
} catch (InvalidArgumentException $e) {
if (str_contains($e->getMessage(), 'missing keys')) {
// the exception names the exact keys — copy them from Setup::$streams defaults into the override
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Setup: Configuration reload loop detected!
- Configuration is missing streams.schemes!
- No backups defined...
- Backup location: {$backup_root} does not exist...
- Cache folder not defined.
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/0861414ac49a71de.
Report an issue: GitHub.