getgrav/grav · error · UnexpectedValueException

{% link with x %}: x is not an array

Error message

{% link with x %}: x is not an array

What it means

The {% link %} tag's compile() method emits PHP that evaluates the `with <expr>` clause into $attributes and immediately checks is_array($attributes); a non-array value throws UnexpectedValueException at template render time. The `with` clause is for HTML attributes and must be a Twig mapping (or an array-valued variable). The sibling `in` clause has its own string check, and the whole check only compiles when a file node is present.

Source

Thrown at system/src/Grav/Common/Twig/Node/TwigNodeLink.php:67

     * @return void
     * @throws LogicException
     */
    public function compile(Compiler $compiler): void
    {
        $compiler->addDebugInfo($this);
        if (!$this->hasNode('file')) {
            return;
        }

        $compiler->write('$attributes = [\'rel\' => \'' . $this->getAttribute('rel') . '\'];' . "\n");
        if ($this->hasNode('attributes')) {
            $compiler
                ->write('$attributes += ')
                ->subcompile($this->getNode('attributes'))
                ->raw(';' . PHP_EOL)
                ->write('if (!is_array($attributes)) {' . PHP_EOL)
                ->indent()
                ->write("throw new UnexpectedValueException('{% {$this->tagName} with x %}: x is not an array');" . PHP_EOL)
                ->outdent()
                ->write('}' . PHP_EOL);
        }

        if ($this->hasNode('group')) {
            $compiler
                ->write('$group = ')
                ->subcompile($this->getNode('group'))
                ->raw(';' . PHP_EOL)
                ->write('if (!is_string($group)) {' . PHP_EOL)
                ->indent()
                ->write("throw new UnexpectedValueException('{% {$this->tagName} in x %}: x is not a string');" . PHP_EOL)
                ->outdent()
                ->write('}' . PHP_EOL);
        } else {
            $compiler->write('$group = \'head\';' . PHP_EOL);
        }

View on GitHub (pinned to 6040efed04)

Solutions

  1. Use a mapping literal: {% link 'style.css' with { rel: 'preload', as: 'style' } %}
  2. If using a variable, ensure it is an array: {% set attrs = {defer: true} %} then {% link 'app.css' with attrs %}
  3. Default null variables before the tag: {% set attrs = attrs ?? {} %}

Example fix

{% set defer = true %}
{# before: scalar passed to with #}
{% link 'app.css' with defer %}

{# after: mapping literal #}
{% link 'app.css' with { defer: true } %}
Defensive patterns

Strategy: validation

Validate before calling

{# in Twig, guard the variable before the tag #}
{% set attrs = attrs is defined and attrs is iterable ? attrs : {} %}
{% link 'app.css' with attrs %}

Try / catch

try { echo $twig->render($template, $data); } catch (UnexpectedValueException $e) { // report template + line from the exception trace
    log_template_error($e); }

Prevention

When it happens

Trigger: {% link 'style.css' with defer %} where defer is a scalar variable; {% link 'x' with 'defer=true' %} (string instead of mapping); a `with` expression that evaluates to null because the variable was never set; passing an object instead of an array.

Common situations: Omitting the braces of the attribute mapping when handwriting the tag; variables expected to hold attribute arrays but left unset after refactors; copying tag examples from docs that show shorthand.

Related errors


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