twigphp/Twig · error · SyntaxError

A "block" tag cannot be under a

Error message

A "block" tag cannot be under a "%s" tag (line %d).

What it means

In a template that extends another, a {% block %} tag cannot appear nested inside another tag's body (e.g. an {% if %}, {% for %}, or {% set %} block), because overridden blocks must be collectable statically at the template root. checkBlockDefinitionNesting throws this SyntaxError naming the offending parent tag and its line.

Solutions

  1. Move the {% block %} to the template's top level and move the condition INSIDE the block
  2. Replace the loop-of-blocks with a single block that loops internally
  3. If conditional inheritance is needed, make the block's content conditional instead of its definition

Example fix

// before
{% if show %}{% block sidebar %}...{% endblock %}{% endif %}
// after
{% block sidebar %}{% if show %}...{% endif %}{% endblock %}
Defensive patterns

Strategy: validation

Validate before calling

if (str_contains($tpl, '{% extends') && preg_match('/{%-?\\s*(if|for|set|spaceless)[^}]*%}(?:(?!{%-?\\s*(if|for|set|spaceless)\\b|block\\b).)*{%-?\\s*block\\b/s', $tpl)) { /* block nested under another tag */ }

Try / catch

try { $env->render($name); } catch (\Twig\Error\SyntaxError $e) { /* block nesting error */ }

Prevention

When it happens

Trigger: Defining {% block %} inside {% if %}, {% for %}, {% spaceless %}, {% set %} etc. in a template that has {% extends %}; the check only runs when hasParent is true and blockDepth/macroDepth/capturingNodeDepth are zero while the tag stack is non-empty.

Common situations: Conditional blocks like {% if x %}{% block sidebar %}...{% endif %} that worked in standalone templates; loops that define a block per iteration; refactoring child templates by wrapping blocks in control-flow tags.

Related errors


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

Appendix: source

Thrown at src/NodeVisitor/CorrectnessNodeVisitor.php:213

        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());
    }

    /**
     * Returns true if the node never outputs anything or if the output is empty.
     */
    private function isEmptyOutputNode(Node $node): bool
    {
        if ($node instanceof NodeCaptureInterface) {
            // a "block" tag in such a node will serve as a block definition AND be displayed in place as well
            return true;
        }

        // Can the text be considered "empty" (only whitespace)?
        if ($node instanceof TextNode) {
            return $node->isBlank();
        }

        if (!$node instanceof BlockReferenceNode && $node instanceof NodeOutputInterface) {

View on GitHub (pinned to a414c3a491)