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

  1. Read the exception message — it names the exact missing keys; add each one back to your streams.schemes override
  2. Prefer additive config: define only your custom scheme under streams.schemes and let Grav's defaults supply the built-ins
  3. Copy the required scheme definitions from system/src/Grav/Common/Config/Setup.php ($streams property) into your override verbatim
  4. 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

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


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