One word is already
a query
Using the client REPLCompletion, ingest and the commands in full
A bare word is matched across the index’s default fields, and several are ORed, so the loosest useful query is one you already know how to write. Naming a field narrows it, in the shape field filtering takes almost everywhere else: field:value, with TO ranges and ^ boosts. If you know Lucene, Elasticsearch or Solr, you already know most of it. Name the id on its own and the search index is skipped entirely. The same parser answers whether a person types the query or an agent sends it over MCP.
termMatch a term; several terms are ORed, so a document matching any one of them is returned. any*Match every document. anyfield:valueMatch a value in one field. text, string, i64, u64, f64, date, boolean, ip, json, facetid:valueLook up one document by id. Fastest retrieval path. anyExact, approximate
or unfinished
Four ways to match words. Phrases and slop work on text fields, and a field prefix works on text and string fields alike.
field:"a b"Exact phrase, terms in order. Text fields only. textfield:"a b"~NPhrase allowing N extra words between terms. text"a b pre"*Phrase whose last term is a prefix. Two or more terms. textfield:pre*Match every term starting with pre. Without a field, a prefix searches the default fields. text, stringEverything ordered
can be bounded
Ranges work on text and string fields as well as the numeric ones, because the term dictionary is ordered. Brackets include the bound, braces exclude it.
field:[low TO high]Range, bounds inclusive. text, string, i64, u64, f64, date, ipfield:{low TO high}Range, bounds exclusive. text, string, i64, u64, f64, date, ipfield:[low TO *]Range with one side unbounded. text, string, i64, u64, f64, date, ipfield:>valueComparison: > < >= <=. Text and string values compare in lexicographic order, as in a range. text, string, i64, u64, f64, datefield: IN [a b c]Match any of several values. text, string, i64, u64, f64, date, booleanfield:/path/to/valueMatch a facet path. facetNarrow it without
starting again
Operators compose. Add a clause with AND or + to narrow a result; clauses without an operator are ORed. That is what makes the grammar easy for an agent to build up step by step.
AND / OR / NOTCombine clauses. Uppercase only. any+term / -termRequire or exclude a clause. any(...)Group clauses to control precedence. anyfield:value^NWeight a clause's score contribution. text, string, i64, u64, f64, date, boolean, ip, json, facetSay what comes back
and how much
Modifiers ride at the end of the query text rather than in a separate parameter, so one string carries the whole request.
return f1,f2Return only these fields, in this order. title:rust return title,authorlimit NCap the number of results. limit 0 is count-only: total_hits and no hits, skipping the key-value store, which is the cheapest way to ask how many documents match. title:rust limit 5offset KSkip the first K results: the page, where limit is the page size. offset + limit is bounded by the same maximum limit is. title:rust limit 10 offset 20sort field:descOrder by a field. See the sorting rules. title:rust sort year:descA modifier counts only where it opens an unbroken run of clauses reaching the end of the query, with query text left in front of it. Anything else is searched for, so find tax return forms is four terms and * limit 10 is how to ask for a bare limit.
A field list needs a comma between names, a limit needs a number, and a sort order must be exactly asc or desc. A clause that does not parse stays in the query rather than being applied in part.
A modifier naming a field the index does not have is reported as a dropped clause, since a projection would otherwise return documents with no fields.
A result says
what it did
Everything the engine adjusted or set aside arrives beside the hits, under a key that appears only when there is something to say. Test for the key, not for an empty value.
_discarded_clausesEach clause the engine set aside, with the form that works._narrowed_default_fieldsThe fields a bare word reached, when the node’s default-field cap kept the search focused._approximate_sortThe field an approximate sort ordered by, with a _warning.stats.timings_msWhere the time went: parse, walk, resolve, fetch and build on the slowest shard, and the node’s own merge.stats.schemaThe schema version and thumbprint the search ran under.404 index_not_foundThe index does not exist. Match on code.408The search ran past the request’s time budget, and answers as a whole or not at all.Exact, approximate
or refused
Ask for an order and one of three things happens, decided from the field's fast column before any shard runs.
Exact
The field has a fast column. describe_index lists those as sortable, and that is the whole test.
Approximate
A text or string field without one. The top 2 × limit matches by relevance are ordered alphabetically: a sample, not the first documents in the index, and a different sample on each page. The response says so, carrying _approximate_sort and a _warning.
Refused
Boolean, bytes, ip, json and facet. The request fails with cannot sort by 'FIELD' and the reason, before any shard runs, so no partial answer comes back with it. Retry on a different field.
A fast column is written when the index is built, so sortable cannot be switched on for an index that already holds data. Declare the field fast before writing to it. That works for text, string, and the numeric and date types, and does nothing on the five that are refused above.
Under a numeric or date sort every hit carries a _score of 1.0, because no relevance is computed. Do not read it as a ranking. Ascending unless desc is given.
Four things decide
whether a clause lands
Which fields a bare word reaches, how a name is written, whether the field is indexed at all, and, for dates, how much of an instant the literal actually means.
A bare word has a reach
A term with no field searches the index’s default fields: its text, string and json fields, or the shorter list it declares as default_fields. Name a field to search exactly there.
A dot is part of the name
Write k8s.node:worker-1 exactly as it reads. Escaping the dot makes the lookup miss, and paths into a json field are not queryable at all.
Indexed, or invisible
A field in the schema but not indexed cannot be queried. Fields discovered from a document arrive unindexed and stay that way until a schema update promotes them, so check the indexed flag before naming one.
A shadow is the id
Where the source data had its own identifier name, the schema keeps the name and only id keeps the value. Querying that name alone is the fastest retrieval there is, answered from the key-value store without the search index; hits come back under the shadow name.
created:2024-06-15An exact instant: midnight precisely. Matches nothing unless a document sits on that second.created:[2024-06-15 TO 2024-06-16}A day. Any span is a range, never a bare literal.created:[2024 TO 2025}A year, the same way.created:"2024-06-15 12:00:00"A space needs quotes, or the remainder reads as a new clause. Writing the T avoids the question.created:>9999-01-01Clamped to 2262-04-11, the far bound Tantivy represents, so it matches nothing rather than erroring.What it will not do
said plainly
A clause the engine can’t interpret is set aside and named in _discarded_clauses, so you always see how your query was read.
field:*Field-presence tests are not supported for any field type. The clause is dropped and reported. Use a bounded range, or match an explicit value.field:*suffixLeading and inner wildcards (*fix, pre*fix) are not supported. The * is dropped, the rest is matched as written, and reported. A trailing * is what makes a prefix.field.subfield:valuePaths into a json field are not queryable. A json field is searchable only as unstructured text, so field:value matches any key or value inside it.field:/regex/Regular expressions are disabled.Reserved characters, which a backslash makes literal: + ^ ` : { } " ' [ ] ( ) ! \ * and space