Shopify/liquid · error · Liquid::StackLevelError
Nesting too deep
Error message
Nesting too deep
What it means
Liquid raises StackLevelError via Context#check_overflow when the template's block nesting exceeds Block::MAX_DEPTH. This guards against runaway recursion in templates (e.g. deeply nested for/if blocks or recursive includes) that would otherwise blow the Ruby stack. It is a protective limit, not a bug in your template data.
Solutions
- Find and break the recursion or reduce nesting depth in the template (often a self-including partial).
- If your use case legitimately needs more depth, raise Block::MAX_DEPTH in an initializer (accepting deeper Ruby stack usage).
- Set a depth/size limit on user-supplied templates before parsing.
- Wrap render in error handling and report the offending template to the user.
Example fix
// before
{% include 'wrapper' %} # wrapper.liquid contains {% include 'wrapper' %}
// after
{% include 'wrapper' %} # wrapper.liquid contains real content, no self-include Defensive patterns
Strategy: validation
Validate before calling
def template_depth_ok?(source)
max_nesting = 0; cur = 0
source.scan(/\{%-?\s*(if|unless|case|for|tablerow|capture|include)\b/) { cur += 1; max_nesting = [max_nesting, cur].max }
source.scan(/\{%-?\s*end\w+/) { cur -= 1 }
max_nesting <= 50 # keep below Block::MAX_DEPTH
end Type guard
def safe_parse?(source) Liquid::Template.parse(source) true rescue Liquid::StackLevelError false end
Try / catch
begin
Liquid::Template.parse(src).render(ctx)
rescue Liquid::StackLevelError => e
logger.warn("Template nesting too deep: #{e.message}")
render_fallback_template
end Prevention
- Never allow templates to include themselves; enforce an include-depth budget.
- Lint templates for nesting depth before deploying.
- Keep Block::MAX_DEPTH defaults unless you have measured stack headroom.
- Reject user-supplied templates with excessive block nesting up front.
When it happens
Trigger: Parsing or rendering a template whose nested block depth (base_scope_depth + scopes) exceeds Block::MAX_DEPTH — e.g. an {% include %} that includes itself, or hundreds of nested {% for %}/{% if %} blocks.
Common situations: Recursive or mutually-recursive includes/partials generated programmatically; templating tools that build deeply nested DOM wrappers; user-supplied templates crafted (maliciously or accidentally) to nest deeply.
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_outer_tag
- errors.syntax.unknown_tag (locale: 'Unknown tag 'tag'')
- undefined method #
- This liquid context does not allow includes.
AI-assisted analysis of Shopify/liquid@807d45a6b3 (2026-09-08).
Data as JSON: /api/errors/8eafe92784da0040.
Report an issue: GitHub.
Appendix: source
Thrown at lib/liquid/context.rb:288
def try_variable_find_in_environments(key, raise_on_not_found:)
@environments.each do |environment|
found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
if !found_variable.nil? || @strict_variables && raise_on_not_found
return found_variable
end
end
@static_environments.each do |environment|
found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
if !found_variable.nil? || @strict_variables && raise_on_not_found
return found_variable
end
end
nil
end
def check_overflow
raise StackLevelError, "Nesting too deep" if overflow?
end
def overflow?
base_scope_depth + @scopes.length > Block::MAX_DEPTH
end
def internal_error
# raise and catch to set backtrace and cause on exception
raise Liquid::InternalError, 'internal'
rescue Liquid::InternalError => exc
exc
end
def squash_instance_assigns_with_environments
@scopes.last.each_key do |k|
@environments.each do |env|
if env.key?(k)
scopes.last[k] = lookup_and_evaluate(env, k)View on GitHub (pinned to 807d45a6b3)