Shopify/liquid · error · Liquid::SyntaxError
Invalid expression type '#
Error message
Invalid expression type '#{invalid_expr}' in range expression What it means
Liquid::RangeLookup validates that both endpoints of a (a..b) range respond to to_i. If a start or end expression evaluates to something non-numeric (e.g. a string like 'abc' or nil that can't be coerced), it raises Liquid::SyntaxError naming the offending markup.
Solutions
- Ensure both range endpoints are integers or numeric strings (e.g. use (1..10) or coerce with plus: 0 / to_i semantics)
- Guard the loop: only iterate when bounds are numeric (use {% if x %} with integer checks)
- Default undefined variables to a numeric value before the range
Example fix
<!-- before -->
{% for i in (1..count) %}...{% endfor %} <!-- count = "abc" -->
<!-- after -->
{% assign n = count | plus: 0 %}
{% for i in (1..n) %}...{% endfor %} Defensive patterns
Strategy: type-guard
Validate before calling
def valid_range?(a, b)
[a, b].all? { |v| !v.nil? && v.to_s =~ /\A-?\d+\z/ }
end Type guard
def integer_like?(v) !v.nil? && (v.is_a?(Integer) || v.to_s =~ /\A-?\d+\z/) end
Try / catch
begin
tpl.render(assigns)
rescue Liquid::SyntaxError => e
raise unless e.message.include?('range expression')
# coerce/default the offending variable and re-render
end Prevention
- Coerce loop bounds with plus: 0 before ranges
- Default undefined variables to numeric values
- Validate external data used as range bounds
When it happens
Trigger: Using a range literal (x..y) in tags like {% for i in (1..x) %} where x or the literal evaluates to a non-integer-convertible value, such as a string 'abc', nil, or a hash.
Common situations: Assigning a variable to a string then using it as a range bound; a variable that is nil/undefined due to a typo; loop bounds coming from user/JSON data that isn't numeric.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- concat filter requires an array argument
- unsafe parse_expression cannot be used in strict2 mode
- Expected # but found #
- Bare bracket access is not allowed. Use #
- # is not a valid expression
AI-assisted analysis of Shopify/liquid@807d45a6b3 (2026-09-08).
Data as JSON: /api/errors/f7f762e6a85347b0.
Report an issue: GitHub.
Appendix: source
Thrown at lib/liquid/range_lookup.rb:17
# frozen_string_literal: true
module Liquid
class RangeLookup
def self.parse(start_markup, end_markup, string_scanner, cache = nil)
start_obj = Expression.parse(start_markup, string_scanner, cache)
end_obj = Expression.parse(end_markup, string_scanner, cache)
if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)
new(start_obj, end_obj)
else
begin
start_obj.to_i..end_obj.to_i
rescue NoMethodError
invalid_expr = start_markup unless start_obj.respond_to?(:to_i)
invalid_expr ||= end_markup unless end_obj.respond_to?(:to_i)
if invalid_expr
raise Liquid::SyntaxError, "Invalid expression type '#{invalid_expr}' in range expression"
end
raise
end
end
end
attr_reader :start_obj, :end_obj
def initialize(start_obj, end_obj)
@start_obj = start_obj
@end_obj = end_obj
end
def evaluate(context)
start_int = to_integer(context.evaluate(@start_obj))
end_int = to_integer(context.evaluate(@end_obj))
start_int..end_intView on GitHub (pinned to 807d45a6b3)