Shopify/liquid · error · Liquid::SyntaxError

errors.syntax.for_invalid_in

errors.syntax.for_invalid_in

Error message

options[:locale].t("errors.syntax.for_invalid_in")

What it means

Raised by strict_parse for {% for %} when the token immediately after the loop variable is not the keyword 'in'. In strict parsing mode Liquid requires the canonical 'for <var> in <collection>' grammar and refuses lax-style shorthand.

Solutions

  1. Use the exact keyword 'in' between variable and collection: {% for item in collection %}
  2. Remove quotes or unusual separators around the loop variable
  3. Audit all for tags when enabling strict mode; use a lint pass to find non-'in' forms

Example fix

// before
{% for item of products %}...{% endfor %}
// after
{% for item in products %}...{% endfor %}
Defensive patterns

Strategy: validation

Validate before calling

valid = markup =~ /\A\s*[\w\-\"']+\s+in\s+.+\z/
raise Liquid::SyntaxError, "expected 'for x in y'" unless valid

Type guard

def strict_for_ok?(markup)
  parts = markup.to_s.strip.split(/\s+/, 3)
  parts.length >= 3 && parts[1] == 'in'
end

Try / catch

begin
  Liquid::Template.parse(src, error_mode: :strict)
rescue Liquid::SyntaxError => e
  logger.error("Strict-mode for-tag error: #{e.message}")
  raise
end

Prevention

When it happens

Trigger: Rendering with the strict parser (error_mode: :strict) and a for tag like {% for item products %}, {% for item of products %}, or {% for 'item' in products %} where the second token isn't the id 'in'.

Common situations: Migrating templates from lax to strict error mode (e.g. Shopify theme checks or Rails apps switching error_mode) exposes previously-tolerated typos like 'for x of y'.

Related errors


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

Appendix: source

Thrown at lib/liquid/tags/for.rb:93

    def lax_parse(markup)
      if markup =~ Syntax
        @variable_name   = Regexp.last_match(1)
        collection_name  = Regexp.last_match(2)
        @reversed        = !!Regexp.last_match(3)
        @name            = "#{@variable_name}-#{collection_name}"
        @collection_name = parse_expression(collection_name)
        markup.scan(TagAttributes) do |key, value|
          set_attribute(key, value)
        end
      else
        raise SyntaxError, options[:locale].t("errors.syntax.for")
      end
    end

    def strict_parse(markup)
      p = @parse_context.new_parser(markup)
      @variable_name = p.consume(:id)
      raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in") unless p.id?('in')

      collection_name  = p.expression
      @collection_name = parse_expression(collection_name, safe: true)

      @name     = "#{@variable_name}-#{collection_name}"
      @reversed = p.id?('reversed')

      while p.look(:comma) || p.look(:id)
        p.consume?(:comma)
        unless (attribute = p.id?('limit') || p.id?('offset'))
          raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_attribute")
        end
        p.consume(:colon)
        set_attribute(attribute, p.expression, safe: true)
      end
      p.consume(:end_of_string)
    end

View on GitHub (pinned to 807d45a6b3)