Shopify/liquid · error · Liquid::MethodOverrideError

Filter overrides registered public methods as non public: #

Error message

Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}

What it means

`StrainerTemplate.add_filter` raises Liquid::MethodOverrideError when a filter module defines a method that collides with an already-registered invokable method but as private/protected instead of public. This protects Liquid's internal invariants: any method invokable from templates must remain public, otherwise filter invocation would break silently.

Solutions

  1. Rename the private/protected method so it does not collide with registered public methods.
  2. Move the `private` keyword below the invokable filter methods, or make the colliding method public.
  3. Audit custom filter modules with `MyFilter.private_instance_methods & Liquid::StrainerTemplate.methods` before registration.
  4. Split helper logic into a separate non-filter module included only for internal use.
  5. Update the filter registration order or remove duplicate conflicting filter modules.

Example fix

// before
module MyFilter
  private
  def truncate(input, n)
    input[0, n]
  end
end
// after
module MyFilter
  def truncate(input, n)
    input[0, n]
  end
  private :truncate # only if it is NOT meant to be invokable — otherwise keep public
end
Defensive patterns

Strategy: validation

Validate before calling

# before register_filter
conflicts = (MyFilter.private_instance_methods + MyFilter.protected_instance_methods) & Liquid::StrainerTemplate.instance_methods
raise "visibility conflict: #{conflicts}" unless conflicts.empty?

Type guard

def safe_to_register?(filter)
  (filter.private_instance_methods + filter.protected_instance_methods)
    .none? { |m| Liquid::StrainerTemplate.method_defined?(m) }
end

Try / catch

begin
  Liquid::Template.register_filter(MyFilter)
rescue Liquid::MethodOverrideError => e
  logger.error("filter registration failed: #{e.message}")
end

Prevention

When it happens

Trigger: Registering a filter module with `Liquid::Template.register_filter(MyFilter)` where MyFilter defines a private/protected instance method whose name matches an existing public invokable method (e.g. redefining `size`, `capitalize` or a core filter as private).

Common situations: Custom filter modules inheriting from a base class that marks methods private; refactoring a public filter to a private helper without renaming; loading two filter modules where the second narrows visibility of a shared method name; ruby `private` keyword accidentally placed above filter definitions.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at lib/liquid/strainer_template.rb:22

module Liquid
  # StrainerTemplate is the computed class for the filters system.
  # New filters are mixed into the strainer class which is then instantiated for each liquid template render run.
  #
  # The Strainer only allows method calls defined in filters given to it via StrainerFactory.add_global_filter,
  # Context#add_filters or Template.register_filter
  class StrainerTemplate
    def initialize(context)
      @context = context
    end

    class << self
      def add_filter(filter)
        return if include?(filter)

        invokable_non_public_methods = (filter.private_instance_methods + filter.protected_instance_methods).select { |m| invokable?(m) }
        if invokable_non_public_methods.any?
          raise MethodOverrideError, "Filter overrides registered public methods as non public: #{invokable_non_public_methods.join(', ')}"
        end

        include(filter)

        filter_methods.merge(filter.public_instance_methods.map(&:to_s))
      end

      def invokable?(method)
        filter_methods.include?(method.to_s)
      end

      def inherited(subclass)
        super
        subclass.instance_variable_set(:@filter_methods, @filter_methods.dup)
      end

      def filter_method_names
        filter_methods.map(&:to_s).to_a

View on GitHub (pinned to 807d45a6b3)