twigphp/Twig · error · SyntaxError
Cannot use "extend" in a macro.
Error message
Cannot use "extend" in a macro.
What it means
Twig forbids an {% extends %} tag inside a {% macro %}. Macros are self-contained callable fragments compiled as standalone units and cannot change the template's inheritance, so checkExtendsTag throws a SyntaxError when macroDepth is non-zero.
Solutions
- Move {% extends %} out of the macro to the template's top level
- Use {% include %} inside the macro instead of extends if you need to render another template
- Extract the macro body into a separate template that itself declares the extends
Example fix
// before
{% macro wrap() %}
{% extends 'base.html.twig' %}
{% endmacro %}
// after
{% extends 'base.html.twig' %}
{% import _self as m %}{% block content %}{{ m.wrap() }}{% endblock %} Defensive patterns
Strategy: validation
Validate before calling
if (preg_match('/{%-?\\s*extends\\b/', $macroBody)) { throw new \InvalidArgumentException('extends not allowed inside macro'); } Try / catch
try { $env->render($name); } catch (\Twig\Error\SyntaxError $e) { /* invalid extends placement */ } Prevention
- Macros are self-contained: use {% include %} for nesting templates
- Keep extends tags at template top level
- Review generated templates for macro-wrapped inheritance
When it happens
Trigger: An {% extends %} tag placed inside {% macro name() %} ... {% endmacro %}; detected in checkExtendsTag when $this->macroDepth > 0.
Common situations: Wrapping an entire template (extends included) into a macro for reuse; code generation that wraps template chunks in macros; confusion between macros and includes when factoring out shared markup.
Related errors
- Calling the "parent" function outside of a block is…
- Calling the "parent" function on a template that does not…
- A template that extends another one cannot include content…
- Cannot use "extend" in a block.
- Multiple extends tags are forbidden.
AI-assisted analysis of twigphp/Twig@a414c3a491 (2026-09-13).
Data as JSON: /api/errors/c2d3c3281e5fdbbc.
Report an issue: GitHub.
Appendix: source
Thrown at src/NodeVisitor/CorrectnessNodeVisitor.php:193
private function checkConfigTag(ConfigNode $node): void
{
if ('extends' === $node->getNodeTag()) {
$this->checkExtendsTag($node);
}
if (!isset($this->rootNodes[$node])) {
trigger_deprecation('twig/twig', '3.28', 'Using the "%s" tag outside the root of a template is deprecated in %s at line %d.', $node->getNodeTag(), $node->getSourceContext()->getName(), $node->getTemplateLine());
}
}
private function checkExtendsTag(ConfigNode $node): void
{
// "extends" inside a "block" or a "macro" has always been a hard error; keep it
if ($this->blockDepth) {
throw new SyntaxError('Cannot use "extend" in a block.', $node->getTemplateLine(), $node->getSourceContext());
}
if ($this->macroDepth) {
throw new SyntaxError('Cannot use "extend" in a macro.', $node->getTemplateLine(), $node->getSourceContext());
}
if ($this->hasExtends) {
throw new SyntaxError('Multiple extends tags are forbidden.', $node->getTemplateLine(), $node->getSourceContext());
}
$this->hasExtends = true;
}
private function checkBlockDefinitionNesting(BlockReferenceNode $node): void
{
// A "block" definition nested under an output-wrapping tag is registered globally
// regardless of that tag, so the nesting is misleading. This only matters at the root
// of a child template's body: once inside a block, a macro, an output capture, or in
// a standalone template, the block is rendered in place and behaves like any other.
if (!$this->hasParent || $this->blockDepth || $this->macroDepth || $this->capturingNodeDepth || !$this->tagStack) {
return;
}
View on GitHub (pinned to a414c3a491)