Shopify/liquid · error · Liquid::SyntaxError
errors.syntax.variable_termination
Error message
errors.syntax.variable_termination (locale: "Variable '{token}' was not properly terminated with regexp: {tag_end}") What it means
Liquid raises this SyntaxError when a `{{ ... }}` output expression is never closed with `}}`. raise_missing_variable_terminator mirrors raise_missing_tag_terminator but for variables, using the VariableEnd regexp (`\}\}`) in the message.
Solutions
- Close the variable with `}}` as shown in the error message.
- If using another templating layer alongside Liquid, change one layer's delimiters or escape braces.
- Balance nested braces/quotes inside the expression.
- Validate templates with Liquid::Template.parse in tests before rendering in production.
Example fix
// before
{{ product.title
// after
{{ product.title }} Defensive patterns
Strategy: validation
Validate before calling
src.each_line do |l|
opens = l.scan('{{').size
closes = l.scan('}}').size
warn "unbalanced braces: #{l}" if opens != closes
end Type guard
null
Try / catch
begin
Liquid::Template.parse(src)
rescue Liquid::SyntaxError => e
if e.message.include?('Variable') && e.message.include?('terminated')
logger.error("unterminated liquid variable: #{e.message}")
fallback_template
else
raise
end
end Prevention
- Change delimiters when mixing Liquid with Vue/Jinja in one file, or escape braces.
- Count {{ vs }} per line in a lint check.
- Avoid hand-building templates via string concatenation that drops braces.
- Parse-test templates in CI.
When it happens
Trigger: Writing `{{ user.name` without `}}`; a `}}` consumed by an inner string or nested braces like `{{ a{{b }}`; template truncation; frameworks that treat `{{ }}` as their own syntax (Vue, Jinja conflicts) stripping delimiters.
Common situations: Mixing Liquid with Jinja2/Vue/Handlebars in one file; editors or linters auto-removing braces; heredocs/templated configs where escaping eats a brace; hand-rolled string interpolation.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- errors.syntax.unexpected_else (locale: Unexpected 'else'…
- errors.syntax.invalid_delimiter
- errors.syntax.unknown_tag (locale: 'Unknown tag 'tag'')
- errors.syntax.tag_never_closed
- errors.syntax.tag_termination
AI-assisted analysis of Shopify/liquid@807d45a6b3 (2026-09-08).
Data as JSON: /api/errors/c7ffd7b8de4e9cdc.
Report an issue: GitHub.
Appendix: source
Thrown at lib/liquid/block_body.rb:82
parse_context.line_number = tokenizer.line_number
end
yield nil, nil
end
# @api private
def self.unknown_tag_in_liquid_tag(tag, parse_context)
Block.raise_unknown_tag(tag, 'liquid', '%}', parse_context)
end
# @api private
def self.raise_missing_tag_terminator(token, parse_context)
raise SyntaxError, parse_context.locale.t("errors.syntax.tag_termination", token: token, tag_end: TagEnd.inspect)
end
# @api private
def self.raise_missing_variable_terminator(token, parse_context)
raise SyntaxError, parse_context.locale.t("errors.syntax.variable_termination", token: token, tag_end: VariableEnd.inspect)
end
# @api private
def self.render_node(context, output, node)
node.render_to_output_buffer(context, output)
rescue => exc
blank_tag = !node.instance_of?(Variable) && node.blank?
rescue_render_node(context, output, node.line_number, exc, blank_tag)
end
# @api private
def self.rescue_render_node(context, output, line_number, exc, blank_tag)
case exc
when MemoryError
raise
when UndefinedVariable, UndefinedDropMethod, UndefinedFilter
context.handle_error(exc, line_number)
elseView on GitHub (pinned to 807d45a6b3)