twigphp/Twig · error · SyntaxError

Multiple extends tags are forbidden.

Error message

Multiple extends tags are forbidden.

What it means

A template may extend only one parent, so a second {% extends %} tag makes inheritance ambiguous. checkExtendsTag throws this SyntaxError when hasExtends is already true (a previous extends tag was seen in the same template).

Solutions

  1. Keep exactly one {% extends %} tag per template
  2. Use conditional inheritance with a single tag: {% extends condition ? 'a.html.twig' : 'b.html.twig' %}
  3. Remove the leftover duplicate extends line from a merge/refactor

Example fix

// before
{% extends 'a.html.twig' %}
{% extends 'b.html.twig' %}
// after
{% extends is_mobile ? 'mobile.html.twig' : 'base.html.twig' %}
Defensive patterns

Strategy: validation

Validate before calling

$n = preg_match_all('/{%-?\\s*extends\\b/', $tpl); if ($n > 1) { throw new \InvalidArgumentException('multiple extends tags'); }

Try / catch

try { $env->render($name); } catch (\Twig\Error\SyntaxError $e) { if (str_contains($e->getMessage(), 'Multiple extends')) { /* fix template */ } }

Prevention

When it happens

Trigger: Two or more {% extends %} tags in one template file; also triggered via Parser::setParent when setParent is called a second time with throwOnMultiple=true.

Common situations: Concatenating or merging templates so two extends lines remain; conditional inheritance attempts where developers expect only one branch to count — Twig parses all tags statically; copy-paste duplicates left after refactoring.

Understand the failure class

Related errors


AI-assisted analysis of twigphp/Twig@a414c3a491 (2026-09-13). Data as JSON: /api/errors/e5607cedc9b1871f. Report an issue: GitHub.

Appendix: source

Thrown at src/NodeVisitor/CorrectnessNodeVisitor.php:196

            $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;
        }

        $tag = $this->tagStack[array_key_last($this->tagStack)];
        throw new SyntaxError(\sprintf('A "block" tag cannot be under a "%s" tag (line %d).', $tag->getNodeTag(), $tag->getTemplateLine()), $node->getTemplateLine(), $node->getSourceContext());
    }

View on GitHub (pinned to a414c3a491)