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

  1. Move {% extends %} out of the macro to the template's top level
  2. Use {% include %} inside the macro instead of extends if you need to render another template
  3. 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

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


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)