twigphp/Twig · error · SyntaxError

A block must start with a tag name.

Error message

A block must start with a tag name.

What it means

After a {% token start, the parser expects an identifier (the tag name such as if, block, extends). If the next token is not a NAME_TYPE token, Parser::subparse throws this SyntaxError because there is no valid Twig tag to dispatch to.

Solutions

  1. Fix the tag so it starts with a valid tag name after {%
  2. Wrap literal {{ or {% sequences in {% raw %} ... {% endraw %} (or {% verbatim %})
  3. Check for stray/mismatched {% delimiters from string interpolation or JS templating
  4. If generating templates, validate that every {% is followed by an identifier

Example fix

// before
{% 123 %}
// after
{% if page == 123 %}...{% endif %}
Defensive patterns

Strategy: validation

Validate before calling

if (preg_match('/{%(?!\\s*[a-zA-Z_][a-zA-Z0-9_]*)[^%]*%}/', $tpl)) { /* tag does not start with a name */ }

Try / catch

try { $env->render($name); } catch (\Twig\Error\SyntaxError $e) { if (str_contains($e->getMessage(), 'block must start with')) { /* malformed tag */ } }

Prevention

When it happens

Trigger: Typing an invalid tag start like {% 123 %}, {% "foo" %}, {% %}, or {% $var %}; unescaped stray braces that begin a block; broken operators such as {% == x %}.

Common situations: Typos in templates; JavaScript or CSS containing {{/{% sequences not wrapped in {% raw %} or {% verbatim %}; templating a language whose syntax collides with Twig delimiters; generation bugs producing empty tag bodies.

Related errors


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

Appendix: source

Thrown at src/Parser.php:201

                    $token = $this->stream->next();
                    $rv[] = new TextNode($token->getValue(), $token->getLine());
                    break;

                case $this->stream->getCurrent()->test(Token::VAR_START_TYPE):
                    $token = $this->stream->next();
                    $expr = $this->parseExpression();
                    $this->stream->expect(Token::VAR_END_TYPE);
                    $node = new PrintNode($expr, $token->getLine());
                    NodeDocumentation::add($node, $token);
                    $rv[] = $node;
                    break;

                case $this->stream->getCurrent()->test(Token::BLOCK_START_TYPE):
                    $startToken = $this->stream->next();
                    $token = $this->getCurrentToken();

                    if (!$token->test(Token::NAME_TYPE)) {
                        throw new SyntaxError('A block must start with a tag name.', $token->getLine(), $this->stream->getSourceContext());
                    }

                    if (null !== $test && $test($token)) {
                        if ($dropNeedle) {
                            $this->stream->next();
                        }

                        if (1 === \count($rv)) {
                            return $rv[0];
                        }

                        return new Nodes($rv, $lineno);
                    }

                    if (!$subparser = $this->env->getTokenParser($token->getValue())) {
                        if (null !== $test) {
                            $e = new SyntaxError(\sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext());

View on GitHub (pinned to a414c3a491)