twigphp/Twig · error · SyntaxError

Expected endblock for block

Error message

Expected endblock for block "%s" (but "%s" given).

What it means

Thrown by BlockTokenParser::parse when a {% block %} is closed with {% endblock name %} whose name does not match the opening block's name. Twig checks the optional name after endblock for consistency and raises a SyntaxError pointing at the endblock line. The mismatch almost always indicates a copy-paste or nesting mistake.

Solutions

  1. Change the name after endblock to match the opening block name.
  2. Remove the name entirely: plain {% endblock %} is always valid.
  3. Check block nesting — the mismatched endblock may be closing the wrong block.
  4. Rename the opening {% block %} if the endblock name is the intended one.

Example fix

{% block title %}Hello{% endblock header %}

{# after #}
{% block title %}Hello{% endblock %}
Defensive patterns

Strategy: validation

Try / catch

try {
    $twig->parse($twig->tokenize(new \Twig\Source($code, $name)));
} catch (\Twig\Error\SyntaxError $e) {
    if (str_contains($e->getMessage(), 'Expected endblock')) {
        // surface template name/line in build output
    }
    throw $e;
}

Prevention

When it happens

Trigger: Writing {% block title %}...{% endblock header %} — the NAME token after endblock differs from the opening block name (loose != comparison, so case differs count as mismatch only for different strings).

Common situations: Copy-pasting blocks and forgetting to update the endblock label; misreading nesting where an inner endblock closes the wrong block; automated refactors that renamed the opening tag only.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at src/TokenParser/BlockTokenParser.php:52

final class BlockTokenParser extends AbstractTokenParser
{
    public function parse(Token $token): Node
    {
        $lineno = $token->getLine();
        $stream = $this->parser->getStream();
        $name = $stream->expect(Token::NAME_TYPE)->getValue();
        $this->parser->setBlock($name, $block = new BlockNode($name, new EmptyNode(), $lineno));
        $this->parser->setDocumentationTarget($block);
        $this->parser->pushLocalScope();
        $this->parser->pushBlockStack($name);

        if ($stream->nextIf(Token::BLOCK_END_TYPE)) {
            $body = $this->parser->subparse([$this, 'decideBlockEnd'], true);
            if ($token = $stream->nextIf(Token::NAME_TYPE)) {
                $value = $token->getValue();

                if ($value != $name) {
                    throw new SyntaxError(\sprintf('Expected endblock for block "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext());
                }
            }
        } else {
            $body = new Nodes([
                new PrintNode($this->parser->parseExpression(), $lineno),
            ]);
        }
        $stream->expect(Token::BLOCK_END_TYPE);

        $block->setNode('body', $body);
        $this->parser->popBlockStack();
        $this->parser->popLocalScope();

        return new BlockReferenceNode($name, $lineno);
    }

    public function decideBlockEnd(Token $token): bool
    {

View on GitHub (pinned to a414c3a491)