The JSON Request API

This section documents the current Solr line (10.0; 9.10.x the maintained 9.x branch), written and verified against the Apache Solr Reference Guide. No specific patch version is pinned. Some capabilities (the Solr Operator on Kubernetes, the package-manager ecosystem, Learning To Rank model training, and expert plugin development) are linked, not documented in depth.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Every example on the preceding pages built a request as a URL query string — q=…​&fq=…​&sort=…​ — appended to /select. That works for a quick curl, but application code that assembles query strings by hand runs into the same problems URL-encoded request bodies always do: nesting is awkward, boolean structure has to be flattened into repeated fq parameters, and a query built from untrusted input is one missed escape away from a broken (or manipulable) request. The JSON Request API replaces the query string with a single JSON document POSTed to the handler, and the JSON Query DSL gives that document’s query/filter keys the same expressive power as the standard, DisMax, and eDisMax parsers — structured as nestable objects instead of \{!…​} local-params strings. This page covers both, plus ParamSets and the Request Parameters API for storing shared parameter sets server-side instead of repeating them on every request.

Sending a JSON request

POST a JSON body to the same /select handler used for GET requests; Solr merges it with any query parameters on the URL (JSON body wins on conflict) and runs the search exactly as it would from a query string.

curl "http://localhost:8983/solr/books/select" \
  -H 'Content-Type: application/json' \
  -d '{
        "query": "title:darkness",
        "filter": "genre_ss:science fiction",
        "limit": 10,
        "sort": "published_at desc"
      }'
# https://solr.apache.org/guide/solr/latest/query-guide/json-request-api.html

query and filter accept a plain query string (parsed by the default lucene parser, or by whatever defType says), an explicit local-params string, or — the point of this page — a JSON object naming a parser and its arguments. limit/offset/sort/fields/facet are JSON spellings of rows/start/sort/fl/json.facet; see Query basics & parameters for what each one does at the parameter level — the JSON API changes how you write a request, not what the underlying parameters mean.

JSON Query DSL

A query (or filter) value can be a JSON object whose single key names a query parser and whose value carries that parser’s arguments — the JSON equivalent of a \{!parser arg=val} local-params string, without the string-escaping problems of building one by hand.

{
  "query": {
    "edismax": {
      "query": "darkness",
      "qf": "title^2 author",
      "mm": "75%"
    }
  }
}
curl "http://localhost:8983/solr/books/select" \
  -H 'Content-Type: application/json' \
  -d '{ "query": { "edismax": { "query": "darkness", "qf": "title^2 author", "mm": "75%" } } }'
# https://solr.apache.org/guide/solr/latest/query-guide/json-query-dsl.html

bool, boost, and lucene clauses

bool composes other clauses — JSON objects, local-params strings, or plain query strings, mixed freely — with must and filter in query context and must_not in negated filter context, the same query/filter split covered on Query basics & parameters:

{
  "query": {
    "bool": {
      "must":     [ { "lucene": { "df": "title", "query": "darkness" } } ],
      "filter":   [ "genre_ss:science fiction", { "lucene": { "query": "in_stock_i:[1 TO *]" } } ],
      "must_not": [ "status:withdrawn" ]
    }
  }
}

lucene is the JSON form of the standard query parser (df/q.op/query arguments); it is what a bare query string is shorthand for. boost wraps a query and multiplies its score by a function, the JSON equivalent of the boost local-params parser used in relevance tuning — see Relevance & scoring for scoring in depth and Function queries for what can appear as the boost function itself:

{
  "query": {
    "boost": {
      "query": { "lucene": { "query": "title:darkness" } },
      "b": "recip(ms(NOW,published_at),3.16e-11,1,1)"
    }
  }
}
curl "http://localhost:8983/solr/books/select" \
  -H 'Content-Type: application/json' \
  -d '{
        "query": {
          "bool": {
            "must":     [ { "lucene": { "df": "title", "query": "darkness" } } ],
            "filter":   [ "genre_ss:science fiction" ],
            "must_not": [ "status:withdrawn" ]
          }
        }
      }'
# https://solr.apache.org/guide/solr/latest/query-guide/json-query-dsl.html

Nested and named queries

A clause can nest another clause of any kind — a bool inside a boost inside a filter, as deep as the logic needs — because every clause is just a JSON value in the same DSL, not a separate sub-language. The queries key declares named queries alongside query/filter; a clause elsewhere in the same request references one by name instead of repeating it:

{
  "queries": {
    "in_print": "status:active"
  },
  "query": {
    "bool": {
      "must":   [ "title:darkness" ],
      "filter": [ { "param": "in_print" } ]
    }
  }
}

Reusing a named query this way keeps a shared condition — an availability filter applied everywhere a request needs it — written once per request instead of copy-pasted into every clause that needs it. Tagging a clause with a leading #tagName inside its parser object works the same as \{!tag=…​} in a local-params string, most commonly to exclude a filter from one facet’s counts; see Faceting for tagging and excluding filters from facet computation.

The params block: the escape hatch

Not every request parameter has a dedicated JSON key. The params block holds anything else — values placed there behave exactly as if they had been appended to the query string, so any parameter documented on Query basics & parameters or a component-specific page works inside it without waiting for the JSON DSL to grow a matching key:

curl "http://localhost:8983/solr/books/select" \
  -H 'Content-Type: application/json' \
  -d '{
        "query": "title:darkness",
        "params": {
          "defType": "edismax",
          "qf": "title^2 author",
          "hl": true,
          "hl.fl": "title"
        }
      }'
# https://solr.apache.org/guide/solr/latest/query-guide/json-request-api.html

params is also where multiple JSON bodies (or a JSON body plus URL parameters) get merged when a request arrives from more than one source at once — later values win, giving predictable precedence instead of a silent pick between conflicting sources.

ParamSets and the Request Parameters API

A params block (or a URL query string) still repeats the same parameters on every request from every client. The Request Parameters API stores a named set of parameters — a ParamSet — once, server-side in params.json (held in ZooKeeper for SolrCloud, in the collection’s conf directory otherwise), and lets any request pull it in by name with useParams instead of restating it.

# Create (or update) a ParamSet named "myFacets".
curl "http://localhost:8983/solr/books/config/params" \
  -H 'Content-Type: application/json' \
  -d '{ "set": { "myFacets": { "facet": "true", "facet.field": "genre_ss", "facet.limit": 10 } } }'

# Apply it to a request -- equivalent to adding facet=true&facet.field=genre_ss&facet.limit=10.
curl --get "http://localhost:8983/solr/books/select" \
  --data-urlencode 'q=*:*' --data-urlencode 'useParams=myFacets'

# View what's stored.
curl "http://localhost:8983/solr/books/config/params/myFacets"
# https://solr.apache.org/guide/solr/latest/configuration-guide/request-parameters-api.html

A ParamSet can hold three kinds of entries: plain defaults (used when the request does not supply the parameter), invariants (win even when the request does supply the parameter, e.g. to pin rows or fq a client cannot override), and appends (added alongside whatever the request sent, e.g. an extra fq that always applies). useParams accepts a comma-separated list, later sets overriding earlier ones, so a request can layer a shared baseline ParamSet under a request-specific one. A request handler’s own solrconfig.xml defaults/invariants/appends can likewise be moved into a ParamSet and referenced from useParams in the handler definition, so shared configuration changes without an solrconfig.xml edit or core reload.

Why the JSON API supersedes hand-built query strings

For application code — as opposed to an ad hoc curl — the JSON Request API is the better default:

  • No hand escaping. A query string built by string concatenation has to URL-encode every value and correctly nest \{!…​} local-params syntax inside it; a JSON body is built (and parsed) with the same JSON library the rest of the application already uses, so nesting and escaping are handled by the serializer, not by hand.

  • Structure matches intent. A bool query’s must/filter/must_not arrays are the actual boolean structure of the request; the query-string equivalent — several fq parameters plus a q string with embedded boolean operators — has to be reconstructed by the reader.

  • Programmatic composition. Building a query object up from conditionals in code (add this filter only if the user supplied a category, add that boost only in one code path) is ordinary object manipulation; the same logic against a query string means careful, error-prone concatenation.

  • Reach beyond q/fq. The JSON Facet API (Faceting) is only reachable through the json.facet parameter or the facet key of a JSON request body — there is no equivalent query-string syntax for its nested, multi-level facets.

  • One document, one diff. A JSON request body is a single value a test can assert against or a code review can diff line-by-line; a query string is a flat, order-sensitive list of key/value pairs.

None of this replaces the query parsers themselves — lucene, dismax, edismax, and the rest still do the actual parsing and matching. The JSON Request API only changes the transport: the same parser arguments that would sit in a \{!…​} local-params string or a query-string parameter instead sit as JSON, sent as the request body rather than assembled into a URL.

Continue with Query parsers for what each parser (lucene, dismax, edismax, and the rest) actually does with these arguments, or Faceting for the JSON Facet API this same request body carries under its facet key.