nodejs/node · error · TemplateSyntaxError

unexpected end of template, expected %r.

Error message

unexpected end of template, expected %r.

What it means

TokenStream.expect(expr) asserts the current token matches an expected expression. When the current token is EOF (TOKEN_EOF) instead, the parser had run out of input while still expecting more — typically an unclosed block or expression. Jinja2 surfaces this as 'unexpected end of template, expected <expr>'.

Source

Thrown at tools/inspector_protocol/jinja2/lexer.py:377

                self.current = next(self._iter)
            except StopIteration:
                self.close()
        return rv

    def close(self):
        """Close the stream."""
        self.current = Token(self.current.lineno, TOKEN_EOF, '')
        self._iter = None
        self.closed = True

    def expect(self, expr):
        """Expect a given token type and return it.  This accepts the same
        argument as :meth:`jinja2.lexer.Token.test`.
        """
        if not self.current.test(expr):
            expr = describe_token_expr(expr)
            if self.current.type is TOKEN_EOF:
                raise TemplateSyntaxError('unexpected end of template, '
                                          'expected %r.' % expr,
                                          self.current.lineno,
                                          self.name, self.filename)
            raise TemplateSyntaxError("expected token %r, got %r" %
                                      (expr, describe_token(self.current)),
                                      self.current.lineno,
                                      self.name, self.filename)
        try:
            return self.current
        finally:
            next(self)


def get_lexer(environment):
    """Return a lexer which is probably cached."""
    key = (environment.block_start_string,
           environment.block_end_string,
           environment.variable_start_string,

View on GitHub (pinned to 1b2de5e052)

Solutions

  1. Locate the unclosed construct: the error line number points at EOF where the parser expected the closing token.
  2. Add the missing {% endif %}, {% endfor %}, {% endblock %}, or {% endmacro %} matching the most recent opening tag.
  3. Close any open ( [ { in expressions and any unterminated {{ }} / {% %} tag.
  4. Use a linting/editor plugin that highlights unclosed Jinja2 blocks.

Example fix

{# before #}
{% if user.active %}
  Hello {{ user.name }}
{# EOF reached, no endif #}

{# after #}
{% if user.active %}
  Hello {{ user.name }}
{% endif %}
Defensive patterns

Strategy: validation

Validate before calling

from jinja2 import Environment

def check_template_balanced(source: str) -> bool:
    env = Environment()
    try:
        env.parse(source)
        return True
    except Exception:
        return False

Try / catch

from jinja2.exceptions import TemplateSyntaxError
try:
    tmpl = env.get_template(name)
except TemplateSyntaxError as e:
    if 'unexpected end of template' in str(e):
        report(f'{name}: unclosed block near line {e.lineno}')
    raise

Prevention

When it happens

Trigger: An unclosed {% if %}/{% for %}/{% block %}/{% macro %} (missing {% endif %} etc.), an unterminated {{ ... }} expression, or an unclosed ( / [ / { inside an expression that runs to the end of the source.

Common situations: Forgetting the closing tag after editing a template; truncating a template during partial load; copy-paste that drops the closing block tag.

Related errors


AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13). Data as JSON: /api/errors/dd5d801a81650f0d. Report an issue: GitHub.