Shopify/liquid · error · Liquid::InternalError

unsafe parse_expression cannot be used in strict2 mode

Error message

unsafe parse_expression cannot be used in strict2 mode

What it means

Liquid's parse_context raises Liquid::InternalError when ParseExpression is called with unsafe markup while the parser is in strict2 mode. In strict2, all expression markup must come from the parser itself (e.g. parser.expression) to guarantee the scanner state stays consistent. The safe flag lets API users opt in deliberately.

Solutions

  1. Extract expression markup via parser.expression (or parser or parser expression helpers) instead of parse_expression with raw strings
  2. Pass the safe flag (parse_expression with safe: true equivalent) after auditing that the markup comes from trusted parser output
  3. Disable strict2 mode if you must keep legacy behavior (not recommended)

Example fix

// before
context.parse_expression(raw_markup)
// after
parser = Liquid::Parser.new(raw_markup)
expr = context.parse_expression(parser.expression)
Defensive patterns

Strategy: try-catch

Validate before calling

raise Liquid::InternalError unless context.strict_parse_compatible?(markup) # ensure markup originates from parser.expression before calling parse_expression

Type guard

def parser_safe?(markup)
  markup.is_a?(String) && markup.frozen? # only parser-produced strings qualify in strict2
end

Try / catch

begin
  context.parse_expression(markup)
rescue Liquid::InternalError => e
  # fall back to parser.expression extraction
  parser = Liquid::Parser.new(markup.to_s)
  context.parse_expression(parser.expression)
end

Prevention

When it happens

Trigger: Calling parse_expression (or ParseContext#parse_expression) with markup that was not produced by the parser while @strict_parse / strict2 mode is enabled, e.g. custom tags handing raw string markup into expression parsing.

Common situations: Custom tag implementations migrated to newer Shopify/Liquid versions with strict parsing enabled; embedding engines (like Shopify themes tooling) that force strict2; reusing legacy tag code that passed ad-hoc markup strings.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of Shopify/liquid@807d45a6b3 (2026-09-08). Data as JSON: /api/errors/850921d1f8ae97a9. Report an issue: GitHub.

Appendix: source

Thrown at lib/liquid/parse_context.rb:66

        line_number: start_line_number,
        for_liquid_tag: for_liquid_tag,
      )
    end

    def safe_parse_expression(parser)
      Expression.safe_parse(parser, @string_scanner, @expression_cache)
    end

    def parse_expression(markup, safe: false)
      if !safe && @error_mode == :strict2
        # parse_expression is a widely used API. To maintain backward
        # compatibility while raising awareness about strict2 parser standards,
        # the safe flag supports API users make a deliberate decision.
        #
        # In strict2 mode, markup MUST come from a string returned by the parser
        # (e.g., parser.expression). We're not calling the parser here to
        # prevent redundant parser overhead.
        raise Liquid::InternalError, "unsafe parse_expression cannot be used in strict2 mode"
      end

      Expression.parse(markup, @string_scanner, @expression_cache)
    end

    def partial=(value)
      @partial = value
      @options = value ? partial_options : @template_options

      @error_mode = @options[:error_mode] || @environment.error_mode
    end

    def partial_options
      @partial_options ||= begin
        dont_pass = @template_options[:include_options_blacklist]
        if dont_pass == true
          { locale: locale }
        elsif dont_pass.is_a?(Array)

View on GitHub (pinned to 807d45a6b3)