Term-level queries

Term-level queries match the exact, un-analyzed value stored in the inverted-indexterm, terms, exists, prefix, wildcard, fuzzy, ids, and range.

Why it matters

They power structured filtering: status enums, IDs, tags, booleans, dates. Because they bypass analysis, they’re the correct tool on keyword, numeric, date, and boolean fields — and the classic footgun on text fields, where the stored tokens are lowercased/stemmed and never equal the raw string.

How it works

The query value is looked up verbatim against the index terms; no tokenizer runs.

QueryMatchesNotes
termone exact termuse on keyword/numeric/boolean
termsany of a listOR over values; supports terms-lookup
existsdocs where field has a valueinverse via must_not
prefixterms starting with Xscans the term dictionary
wildcard*/? globleading * is very slow
fuzzyterms within edit distanceLevenshtein, fuzziness:AUTO
  • Lives in filter context naturally — exact matches rarely need scoring, so wrap in filter for caching.
  • terms lookup — fetch the value list from another document (e.g. a user’s group IDs) instead of inlining.
  • case_insensitive: true (7.10+) lets term/prefix/wildcard ignore case on keyword fields.

Example

{ "bool": { "filter": [
    { "terms": { "tags": ["sale", "clearance"] } },
    { "term":  { "status": "active" } },
    { "exists": { "field": "discount_pct" } } ] } }

If status were a text field analyzed to lowercase, term:"active" would match “ACTIVE” only by luck — always target the keyword sub-field.

Pitfalls

  • term on analyzed text — the #1 mistake; “New York” is indexed as [new, york], so term:"New York" returns nothing. Use match, or term on field.keyword.
  • wildcard/prefix with leading wildcards scan the entire term dictionary per shard — O(unique terms); prefer a reverse field or completion suggester.
  • fuzzy cost — expands to many candidate terms; cap with max_expansions and prefix_length.
  • terms size limitindex.max_terms_count defaults to 65,536; huge IN-lists fail.

See also