Shopify/liquid · error · Liquid::StackLevelError
Nesting too deep
Error message
Nesting too deep
What it means
Liquid raises this StackLevelError (a Liquid::Error subclass, not a SyntaxError) when template nesting depth reaches MAX_DEPTH during parse_body. It guards the recursive block parser against stack exhaustion from deeply or maliciously nested tags, e.g. thousands of nested `{% if %}`s.
Solutions
- Flatten the template logic — combine conditions or restructure to reduce nesting levels.
- If templates come from users, sanitize/limit them before parse (length, tag counts, include depth).
- If recursion is via `{% include %}`, break the include cycle.
- Only raise MAX_DEPTH deliberately and with awareness of Ruby stack limits.
Example fix
// before
{% if a %}{% if b %}{% if c %}... (100+ levels) ...{% endif %}{% endif %}{% endif %}
// after
{% if a and b and c %}
content
{% endif %} Defensive patterns
Strategy: validation
Validate before calling
def nesting_depth(src)
cur = max = 0
src.scan(/{%-?\s*(\w+)/).flatten.each do |t|
if %w[if for case unless tablerow].include?(t)
cur += 1; max = [max, cur].max
elsif t.start_with?('end')
cur -= 1
end
end
max
end
raise 'too deep' if nesting_depth(src) > 50 Type guard
null
Try / catch
begin
Liquid::Template.parse(src)
rescue Liquid::StackLevelError => e
logger.warn("template nesting too deep: #{e.message}")
nil
end Prevention
- Limit user-supplied template complexity (depth/count limits) before parse.
- Combine nested conditions with and/or where possible.
- Break the include cycles that cause runaway recursion.
- Consider a parse timeout/size cap for untrusted templates.
When it happens
Trigger: Parsing a template with more nested block tags than MAX_DEPTH (e.g. >100 nested ifs/fors); recursive includes/includes that reference each other; adversarial user-supplied templates; generated templates with runaway nesting.
Common situations: Accepting user-authored templates (CMS themes, email builders) without depth limits; accidental recursion where a template includes itself; code generators emitting nested conditionals in a loop.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- Nesting too deep
- errors.syntax.unexpected_else (locale: Unexpected 'else'…
- errors.syntax.invalid_delimiter
- errors.syntax.unknown_tag (locale: 'Unknown tag 'tag'')
- errors.syntax.tag_never_closed
AI-assisted analysis of Shopify/liquid@807d45a6b3 (2026-09-08).
Data as JSON: /api/errors/9f615e60d961dfb6.
Report an issue: GitHub.
Appendix: source
Thrown at lib/liquid/block.rb:77
def block_name
@tag_name
end
def block_delimiter
@block_delimiter ||= "end#{block_name}"
end
private
# @api public
def new_body
parse_context.new_block_body
end
# @api public
def parse_body(body, tokens)
if parse_context.depth >= MAX_DEPTH
raise StackLevelError, "Nesting too deep"
end
parse_context.depth += 1
begin
body.parse(tokens, parse_context) do |end_tag_name, end_tag_params|
@blank &&= body.blank?
return false if end_tag_name == block_delimiter
raise_tag_never_closed(block_name) unless end_tag_name
# this tag is not registered with the system
# pass it to the current block for special handling or error reporting
unknown_tag(end_tag_name, end_tag_params, tokens)
end
ensure
parse_context.depth -= 1
end
trueView on GitHub (pinned to 807d45a6b3)