ankane/searchkick · error · ArgumentError

Options incompatible with body option: #

Error message

Options incompatible with body option: #{ignored_options.join(", ")}

What it means

`body:` replaces the entire query payload searchkick would generate, so query-shaping options would be silently ignored — for example `where`, `order`, `aggs`, `boost_by`, `misspellings`, `fields`, `operator`, `highlight`. To prevent silent no-ops, `Query#prepare` intersects your option keys with that list and raises `ArgumentError` listing any incompatible ones when `body:` is present.

Solutions

  1. Remove the listed options and translate their intent into the raw body DSL (bool filter for `where`, sort for `order`, aggs for `aggs`).
  2. Keep the parts that are compatible — `load`, `includes`, `page/per_page/limit/offset`, `index_name`, `models`, `request_params`, `scroll` are not on the forbidden list and still work with `body:`.
  3. If you need both generated and raw pieces, use `body_options:` (merged into the generated payload) instead of `body:`.
  4. Write the body in a named scope/method so the search call stays option-free and greppable.

Example fix

# before
Product.search("milk",
  body: {query: {match_all: {}}, sort: [{price: "asc"}]},
  where: {in_stock: true}, order: {price: :asc}
) # => ArgumentError: Options incompatible with body option: order, where

# after
Product.search(nil,
  body: {
    query: {bool: {must: {match_all: {}}, filter: {term: {"in_stock": true}}}},
    sort: [{price: "asc"}]
  }
)
Defensive patterns

Strategy: validation

Validate before calling

FORBIDDEN_WITH_BODY = %i[aggs boost boost_by boost_by_distance boost_by_recency boost_where conversions conversions_term exclude explain fields highlight indices_boost match misspellings operator order profile select smart_aggs suggest where].freeze

def raw_search(term, body:, **opts)
  bad = opts.keys & FORBIDDEN_WITH_BODY
  raise ArgumentError, "not allowed with body: #{bad.join(', ')}" if bad.any?
  Product.search(term, body: body, **opts)
end

Type guard

def body_safe_options?(opts)
  forbidden = %i[aggs boost boost_by boost_by_distance boost_by_recency boost_where conversions conversions_term exclude explain fields highlight indices_boost match misspellings operator order profile select smart_aggs suggest where]
  (opts.keys & forbidden).empty?
end

Prevention

When it happens

Trigger: `Product.search("milk", body: {query: {...}}, where: {in_stock: true}, order: {price: :asc})` — the `where`/`order` keys trip the check. Also passing `fields:` or `misspellings:` alongside a hand-written body, or wrapping an existing search call with `body:` while leaving old options in place.

Common situations: Graduating from option-based queries to raw ES DSL for one complex query and forgetting to remove the now-dead options; merging code where a `body:` branch is added around an option-heavy call; copy-pasting a raw query from Kibana/Curl into `.search(body: ...)` while keeping surrounding options.

Related errors


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

Appendix: source

Thrown at lib/searchkick/query.rb:271

      max_result_window = searchkick_options[:max_result_window]
      original_per_page = per_page
      if max_result_window
        offset = max_result_window if offset > max_result_window
        per_page = max_result_window - offset if offset + per_page > max_result_window
      end

      # model and eager loading
      load = options[:load].nil? ? true : options[:load]

      all = term == "*"

      @json = options[:body]
      if @json
        ignored_options = options.keys & [:aggs, :boost,
          :boost_by, :boost_by_distance, :boost_by_recency, :boost_where, :conversions, :conversions_term, :exclude, :explain,
          :fields, :highlight, :indices_boost, :match, :misspellings, :operator, :order,
          :profile, :select, :smart_aggs, :suggest, :where]
        raise ArgumentError, "Options incompatible with body option: #{ignored_options.join(", ")}" if ignored_options.any?
        payload = @json
      else
        must_not = []
        should = []

        if options[:similar]
          like = options[:similar] == true ? term : options[:similar]
          query = {
            more_like_this: {
              like: like,
              min_doc_freq: 1,
              min_term_freq: 1,
              analyzer: "searchkick_search2"
            }
          }
          if fields.all? { |f| f.start_with?("*.") }
            raise ArgumentError, "Must specify fields to search"
          end

View on GitHub (pinned to 93e901a75b)