ankane/searchkick · error · ArgumentError

unknown keywords: #{unknown_keywords.join(", ")}

Error message

unknown keywords: #{unknown_keywords.join(", ")}

What it means

Like the model macro, `Model.search(term, **options)` validates its keywords against a query-time allow-list (`:where, :order, :fields, :boost_by, :misspellings, :aggs, ...`). Unknown keys are collected and raised as one `ArgumentError` before any request is sent. Typical causes: model-level options passed at query time, misspellings, or options that only exist in other searchkick versions.

Source

Thrown at lib/searchkick/query.rb:33

      :offset_value, :offset, :previous_page, :prev_page, :next_page, :first_page?, :last_page?,
      :out_of_range?, :hits, :response, :to_a, :first, :scroll, :highlights, :with_highlights,
      :with_score, :misspellings?, :scroll_id, :clear_scroll, :missing_records, :with_hit

    def initialize(klass, term = "*", **options)
      if options[:conversions]
        Searchkick.warn("The `conversions` option is deprecated in favor of `conversions_v2`, which provides much better search performance. Upgrade to `conversions_v2` or rename `conversions` to `conversions_v1`")
      end

      if options.key?(:conversions_v1)
        options[:conversions] = options.delete(:conversions_v1)
      end

      unknown_keywords = options.keys - [:aggs, :block, :body, :body_options, :boost,
        :boost_by, :boost_by_distance, :boost_by_recency, :boost_where, :conversions, :conversions_v2, :conversions_term, :debug, :emoji, :exclude, :explain,
        :fields, :highlight, :includes, :index_name, :indices_boost, :knn, :limit, :load,
        :match, :misspellings, :models, :model_includes, :offset, :opaque_id, :operator, :order, :padding, :page, :per_page, :profile,
        :request_params, :routing, :scope_results, :scroll, :select, :similar, :smart_aggs, :suggest, :total_entries, :track, :type, :where]
      raise ArgumentError, "unknown keywords: #{unknown_keywords.join(", ")}" if unknown_keywords.any?

      term = term.to_s

      if options[:emoji]
        term = EmojiParser.parse_unicode(term) { |e| " #{e.name.tr('_', ' ')} " }.strip
      end

      @klass = klass
      @term = term
      @options = options
      @match_suffix = options[:match] || searchkick_options[:match] || "analyzed"

      # prevent Ruby warnings
      @type = nil
      @routing = nil
      @misspellings = false
      @misspellings_below = nil
      @highlighted_fields = nil

View on GitHub (pinned to 93e901a75b)

Solutions

  1. Compare the listed unknown keys against the allow-list in lib/searchkick/query.rb:23-27 and fix names/locations.
  2. Move model-level options (`batch_size`, `searchable`, `filterable`, `language`, ...) into the `searchkick` macro; keep only query-shaping options in `.search`.
  3. Verify the option exists in your gem version (`Searchkick::VERSION` + the matching README tag on GitHub) and pin the Gemfile accordingly.
  4. Smoke-test one representative `.search` call in a staging console right after gem upgrades.

Example fix

# before
Product.search("milk", filter: {in_stock: true}, page: 1)
# => ArgumentError: unknown keywords: filter

# after
Product.search("milk", where: {in_stock: true}, page: 1)
Defensive patterns

Strategy: validation

Validate before calling

QUERY_OPTIONS = %i[where order fields limit offset page per_page aggs boost_by misspellings operator highlight load includes exclude similar select body body_options index_name models knn].freeze
def product_search(term = "*", **opts)
  unknown = opts.keys - QUERY_OPTIONS
  raise ArgumentError, "unknown search options: #{unknown.join(', ')}" if unknown.any?
  Product.search(term, **opts)
end

Type guard

def valid_search_options?(opts)
  allowed = %i[aggs block body body_options boost boost_by boost_by_distance boost_by_recency boost_where conversions conversions_v2 conversions_term debug emoji exclude explain fields highlight includes index_name indices_boost knn limit load match misspellings models model_includes offset opaqueid operator order padding page per_page profile request_params routing scope_results scroll select similar smart_aggs suggest total_entries track type where]
  (opts.keys - allowed).empty?
end

Prevention

When it happens

Trigger: `Product.search("milk", batch_size: 100)` (model option), `Product.search("milk", filter: {...})` (meant `where:`), `Product.search("milk", lang: "en")`, or any option added/removed across gem versions — raises at query construction, so the first search after deploy fails.

Common situations: Upgrading searchkick majors where options were renamed (e.g. old `misspellings: false` stays valid but others change); copying search calls from README HEAD on an older gem; confusing model-level options (`searchable`, `word_start`, `stemmer`) with query-level ones; passing a typo'd `where` as `whare`.

Related errors


AI-assisted analysis of ankane/searchkick@93e901a75b (2026-08-21). Data as JSON: /api/errors/8ef7d0f06bf83f1d. Report an issue: GitHub.