twigphp/Twig · error · SyntaxError

Unexpected end of template.

Error message

Unexpected end of template.

What it means

TokenStream::next() advances past the last token; when there is no next token, Twig throws 'Unexpected end of template.' This means the template's syntax required more tokens than exist — typically an unclosed construct.

Solutions

  1. Check the reported line (the last parsed line) for an unclosed tag or expression and add the matching end tag
  2. Verify the template file was fully deployed/saved and is not truncated
  3. Run twig-lint (bin/console lint:twig in Symfony) to catch the unclosed construct before deployment

Example fix

// before
{% if user %}
  {{ user.name }}
// after
{% if user %}
  {{ user.name }}
{% endif %}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-compile templates to catch EOF/unclosed constructs before render
$env->parse($env->tokenize(new Source($template, $name)));

Try / catch

try {
    $html = $twig->render($name, $context);
} catch (SyntaxError $e) {
    if (str_contains($e->getMessage(), 'Unexpected end of template')) {
        // log template name + line; alert that a tag is unclosed or file truncated
    }
    throw $e;
}

Prevention

When it happens

Trigger: A template ending while the parser still expects tokens: unclosed {% if %}/{% for %}/{% block %}, an unfinished expression like '{{ foo', or a missing {% endtag %}; any parser call to next()/expect() at EOF triggers it.

Common situations: Truncated template files from failed deploys/partial saves; copy-pasting template fragments without their closing tags; forgetting {% endif %}, {% endfor %}, or {% endblock %}.

Related errors


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

Appendix: source

Thrown at src/TokenStream.php:53

    }

    public function __toString(): string
    {
        return implode("\n", $this->tokens);
    }

    public function injectTokens(array $tokens): void
    {
        $this->tokens = array_merge(\array_slice($this->tokens, 0, $this->current), $tokens, \array_slice($this->tokens, $this->current));
    }

    /**
     * Sets the pointer to the next token and returns the old one.
     */
    public function next(): Token
    {
        if (!isset($this->tokens[++$this->current])) {
            throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current - 1]->getLine(), $this->source);
        }

        return $this->tokens[$this->current - 1];
    }

    /**
     * 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.
     */

View on GitHub (pinned to a414c3a491)