getgrav/grav · error · UnexpectedValueException
{% link in x %}: x is not a string
Error message
{% link in x %}: x is not a string What it means
The {% link %} tag compiles the `in <expr>` clause (the asset group) into $group = <expr> followed by an is_string($group) check; anything else throws UnexpectedValueException when the compiled template runs. The group names where the link asset is placed (default 'head' when the clause is absent) must be a string.
Source
Thrown at system/src/Grav/Common/Twig/Node/TwigNodeLink.php:79
$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);
}
if ($this->hasNode('priority')) {
$compiler
->write('$priority = (int)(')
->subcompile($this->getNode('priority'))
->raw(');' . PHP_EOL);
} else {
$compiler->write('$priority = 10;' . PHP_EOL);
}
$compiler->write("\$assets = \\Grav\\Common\\Grav::instance()['assets'];" . PHP_EOL);
$compiler->write("\$block = \$context['block'] ?? null;" . PHP_EOL);
View on GitHub (pinned to 6040efed04)
Solutions
- Pass a plain string: {% link 'a.css' in 'head' %}
- Default the variable: {% set group = group ?? 'head' %} before the tag
- Cast in the expression if the value may be non-string: in group|default('head')
Example fix
{% set group = ['bottom'] %}
{# before: array passed to in #}
{% link 'app.css' in group %}
{# after: string group name #}
{% link 'app.css' in 'bottom' %} Defensive patterns
Strategy: validation
Validate before calling
{# in Twig, guard the group variable before the tag #}
{% set group = group is defined and group is string ? group : 'head' %}
{% link 'app.css' in group %} Try / catch
try { echo $twig->render($template, $data); } catch (UnexpectedValueException $e) { log_template_error($e); } Prevention
- Use plain string literals for asset groups ('head', 'bottom')
- Default group expressions with |default('head')
- Keep group names as simple scalars in your data model
When it happens
Trigger: {% link 'a.css' in 5 %} (integer literal); {% link 'a.css' in groupVar %} where groupVar is an array or null; a computed expression like in ['head', 'bottom']|first that unexpectedly yields null.
Common situations: Variables used as group names that were never initialized; refactors turning a scalar group into an array; typos where a mapping is passed instead of a bare string.
Related errors
- {% link with x %}: x is not an array
- {% script with x %}: x is not an array
- {% script in x %}: x is not a string
- {% {$this->tagName} with x %}: x is not an array
- {% {$this->tagName} in x %}: x is not a string
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/218feed9da06282d.
Report an issue: GitHub.