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
- Locate the unclosed construct: the error line number points at EOF where the parser expected the closing token.
- Add the missing {% endif %}, {% endfor %}, {% endblock %}, or {% endmacro %} matching the most recent opening tag.
- Close any open ( [ { in expressions and any unterminated {{ }} / {% %} tag.
- 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
- Validate templates with env.parse(source) at load/test time.
- Use an editor with Jinja2 bracket/tag matching.
- Keep blocks small and close tags immediately after opening them.
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
- expected token %r, got %r
- chunk after expression
- Invalid character in identifier
- unexpected '%s'
- unexpected '%s', expected '%s'
AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13).
Data as JSON: /api/errors/dd5d801a81650f0d.
Report an issue: GitHub.