twigphp/Twig · error · SyntaxError

%sUnexpected token " " (" " expected ).

Error message

%sUnexpected token "%s"%s ("%s" expected%s).

What it means

TokenStream::expect() is Twig's generic token-mismatch error: the parser encountered a token of unexpected type/value. The message is prefixed with any custom message (e.g. 'An argument must be a name') and shows what was found versus what was expected.

Solutions

  1. Read the 'Unexpected token X (Y expected)' message and fix the token at the given line to match what was expected
  2. Run a Twig linter on the template to locate the exact mismatch quickly
  3. Check that custom message prefixes in the error for hints about which construct was being parsed (argument list, tag name, etc.)

Example fix

// before
{% for user in users if user.active %}
// after
{% for user in users|filter(u => u.active) %}
Defensive patterns

Strategy: try-catch

Validate before calling

$env->parse($env->tokenize(new Source($template, $name))); // lint before deploy

Try / catch

try {
    $html = $twig->render($name, $context);
} catch (SyntaxError $e) {
    $context = [
        'message' => $e->getMessage(),
        'line' => $e->getTemplateLine(),
        'source' => $e->getSourceContext()->getCode(),
    ];
    // render an editor-facing syntax error page instead of a 500
}

Prevention

When it happens

Trigger: Any syntax deviation the parser checks with expect(): e.g. '{% macro 123() %}' (argument not a name), missing commas/parentheses in expressions or argument lists, wrong tag spelling so a closing tag is expected but not found.

Common situations: Typos in tag names and operators; using reserved keywords as variable names; missing '(', ')', ',', '=', or '%}' in expressions; editing templates by hand without linting.

Understand the failure class

Related errors


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

Appendix: source

Thrown at src/TokenStream.php:77

    /**
     * Tests a token, sets the pointer to the next one and returns it or throws a syntax error.
     *
     * @return Token|null The next token if the condition is true, null otherwise
     */
    public function nextIf($primary, $secondary = null)
    {
        return $this->tokens[$this->current]->test($primary, $secondary) ? $this->next() : null;
    }

    /**
     * Tests a token and returns it or throws a syntax error.
     */
    public function expect($type, $value = null, ?string $message = null): Token
    {
        $token = $this->tokens[$this->current];
        if (!$token->test($type, $value)) {
            $line = $token->getLine();
            throw new SyntaxError(\sprintf('%sUnexpected token "%s"%s ("%s" expected%s).',
                $message ? $message.'. ' : '',
                $token->toEnglish(),
                $token->getValue() ? \sprintf(' of value "%s"', $token->getValue()) : '',
                Token::typeToEnglish($type), $value ? \sprintf(' with value "%s"', $value) : ''),
                $line,
                $this->source,
                columnno: $this->source->getColumn($token->getOffset() ?? -1),
            );
        }
        $this->next();

        return $token;
    }

    /**
     * Looks at the next token.
     */
    public function look(int $number = 1): Token

View on GitHub (pinned to a414c3a491)