twigphp/Twig · error · SyntaxError

Unexpected " ".

Error message

Unexpected "%s".

What it means

Inside an expression, a closing bracket (`)`, `]`, or `}`) appeared when the bracket stack was empty — there is no matching opening bracket. Twig reports the offending character with line and column via checkBrackets, which runs for every punctuation token in lexExpression.

Solutions

  1. Remove the extra closing bracket at the reported line/column.
  2. Add the matching opening bracket if one was intended (e.g. `{{ foo(bar)) }}` → `{{ foo((bar)) }}` review).
  3. Wrap literal braces in `{% verbatim %}` blocks instead of leaving them in expressions.
  4. Re-check the whole expression — an earlier missing `(` often makes a later `)` look extra.

Example fix

// before
{{ items] }}

// after
{{ items }}
Defensive patterns

Strategy: validation

Validate before calling

foreach (['(' => ')', '[' => ']', '{' => '}'] as $open => $close) {
    if (preg_match('/\{\{[^}]*'.preg_quote($close, '/').'\s*\}\}/', $code)) {
        // candidate stray closer inside an expression; review before parsing
    }
}

Try / catch

try {
    $twig->render($name, $context);
} catch (\Twig\Error\SyntaxError $e) {
    log('Unexpected bracket at line '.$e->getTemplateLine());
}

Prevention

When it happens

Trigger: An extra `)` / `]` / `}` in a tag: e.g. `{{ foo) }}`, `{% if (x) %}` written as `{% if x) %}`, or a `}` inside `{{ }}` used literally without quoting. Also caused by a preceding unclosed-bracket removal that unbalanced the expression.

Common situations: Typos in arithmetic or function-call expressions; pasting PHP/JS expressions that use syntax Twig doesn't accept; literal braces in templates placed inside expression context instead of verbatim/raw blocks.

Related errors


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

Appendix: source

Thrown at src/Lexer.php:670

    private function popState(): void
    {
        if (0 === \count($this->states)) {
            throw new \LogicException('Cannot pop state without a previous state.');
        }

        $this->state = array_pop($this->states);
    }

    private function checkBrackets(string $code): void
    {
        // opening bracket
        if (\in_array($code, $this->openingBrackets, true)) {
            $this->brackets[] = [$code, $this->lineno];
        } elseif (\in_array($code, $this->closingBrackets, true)) {
            // closing bracket
            if (!$this->brackets) {
                throw new SyntaxError(\sprintf('Unexpected "%s".', $code), $this->lineno, $this->source, columnno: $this->source->getColumn($this->cursor));
            }

            [$expect, $lineno] = array_pop($this->brackets);
            if ($code !== str_replace($this->openingBrackets, $this->closingBrackets, $expect)) {
                throw new SyntaxError(\sprintf('Unclosed "%s".', $expect), $lineno, $this->source);
            }
        }
    }
}

View on GitHub (pinned to a414c3a491)