Elasticsearch Search Relevance
Improve full-text search results on content and catalog indices. Diagnose the mapping and current query, choose the
right relevance lever (query rules for deterministic pinning vs multi_match and field boosts for organic ranking), apply
the change, and verify top hits before reporting success.
Environment Configuration
This skill executes Elasticsearch operations through the elastic CLI. If the
elastic CLI is not installed, tell the user what it is needed for. Do
not guess credentials, call the HTTP API directly, or attempt other workarounds.
This skill references operations in HTTP-shorthand form (e.g., GET /, GET /_cat/indices, GET /{index}/_mapping,
GET /{index}/_settings/index.mode, POST /_query). The Operations table at the end of this document
maps each shorthand to the equivalent elastic CLI command — always use the CLI rather than calling the HTTP API
directly.
Scope
This skill covers Query DSL relevance on indices with text (and optional keyword) fields — product catalogs,
documentation, knowledge bases. It uses POST /{index}/_search for evaluation and query-rules APIs for pinned or
excluded documents.
Out of scope:
- ES|QL search (
POST /_query) — use the elasticsearch-esql skill.
- Semantic / vector / hybrid retrieval — different field types and retrievers.
- Sorting by price, date, or popularity instead of fixing text relevance unless the user explicitly wants
non-relevance ordering.
Relevance levers
| User intent |
Lever |
APIs |
| Always show document X first for query Q |
Query rules — pinned rule + rule query in search |
PUT /_query_rules/{ruleset_id}, POST /{index}/_search |
| Hide specific documents for query Q |
Query rules — exclude rule + rule query |
Same |
| Better ranking for open-ended text queries |
multi_match across mapped text fields with field boosts |
POST /{index}/_search |
| Tokens not matching user language |
Operator, minimum_should_match, or synonym analyzers |
POST /{index}/_search, optionally POST /{index}/_analyze |
Decision rule: If the user names a document that must rank first for a specific query, use query rules. If results
are generally weak for a phrase, tune the organic query from the mapping. Do not simulate pinning with extreme boosts,
function_score, or sort clauses.
Process
Inspect the mapping and current query. Call GET / to confirm connectivity. When the index is unknown, narrow
candidates with GET /_cat/indices, then call GET /{index}/_mapping.
From the mapping, list every text field (e.g., title, description) and every keyword field used for filters
(brand, category). Note which fields are short (precision) vs long (recall). Read the user's current search body
if provided — identify which fields it queries and whether it already uses rule, multi_match, or single-field
match.
Decision: Is the problem deterministic promotion (one doc must win for one query) or organic ranking
(several docs should score better)? Data needed: index name, mapping properties, current query JSON, example
query strings, and target document ID(s) when pinning.
Choose the relevance lever. Apply the decision from step 1:
Pinning / promotion → Create a query-rules ruleset with a rule of type pinned (never exclude for
promotion). Set criteria so the rule fires only for the intended query text — e.g., contains or exact on a
metadata key such as query_string with value "sale". Set actions to pin the correct document via ids (e.g.,
["SKU123"]) or docs (e.g., [{"_index":"catalog","_id":"SKU123"}]). Use docs when _id may not be unique
across indices. Read Query Rules Reference for full structure.
Organic ranking → Replace single-field match on a long field with multi_match across the mapped text
fields. Boost short fields (typically title^2 with description unboosted). Consider operator,
minimum_should_match, or synonym-aware analyzers when multi-word recall is still poor — but do not sort by
price, date, or keyword fields to fake better text relevance, and do not query .keyword sub-fields with
term for analyzed user phrases. Read Multi-Match Tuning.
Decision: Pick exactly one primary lever per request. Data needed: chosen fields and boosts, ruleset ID and
rule ID names, criteria metadata keys, and pinned document identifiers.
Apply the change. Execute the APIs for the chosen lever:
Query rules path
- Create or replace the ruleset with
PUT /_query_rules/{ruleset_id} (or add one rule with
PUT /_query_rules/{ruleset_id}/_rule/{rule_id}).
- Confirm structure with
GET /_query_rules/{ruleset_id}.
- Validate criteria with
POST /_query_rules/{ruleset_id}/_test using the same match_criteria you will pass at
search time.
- Wire the search:
POST /{index}/_search must use a rule query whose ruleset_id references the ruleset and
whose match_criteria supplies values for every criteria metadata key (e.g., "query_string": "sale"). Place
the normal relevance clause inside organic. Creating the ruleset alone does not pin anything — the pin
applies only when search includes the rule query.
Organic tuning path
- Build a candidate
multi_match (or equivalent bool/should) query from the mapping.
- Optionally inspect analysis with
POST /{index}/_analyze on sample query text when tokenization explains misses.
Decision: Stop after one coherent change set; avoid stacking unrelated edits before testing.
Test and compare top hits. Before and after each candidate, call POST /{index}/_search with the same size (≥
10), the user's query string, and "track_scores": true. For pinning, the search body must include the rule
query from step 3.
Compare for each run:
- Top
_id values and order
_score where relevant
- Key
_source fields (title, description, product id)
For pinning, confirm the target document (e.g., SKU123) is first when match_criteria matches the query and
that organic matches still appear below. For organic tuning, confirm titles and intent-aligned documents rise without
relying on sort or keyword exact-match hacks.
Decision: Ship the candidate that wins on evidence; if none improve results, report what was tried and propose
the next lever (e.g., synonyms or additional fields). Data needed: side-by-side top-hit lists from baseline and
candidate queries.
Examples
Pin SKU123 for query "sale" on catalog
Wrong: Boost SKU123, sort by _id, or create a ruleset without a rule search query.
Right:
PUT /_query_rules/catalog-sale-pin with a pinned rule, criteria matching query text "sale", actions pinning
SKU123.
POST /catalog/_search with:
{
"query": {
"rule": {
"ruleset_id": "catalog-sale-pin",
"match_criteria": { "query_string": "sale" },
"organic": {
"multi_match": {
"query": "sale",
"fields": ["title^2", "description"]
}
}
}
},
"size": 10
}
Verify SKU123 is hit #1 and remaining hits are organic matches below the pin.
Improve "running shoes" when only description is searched
Mapping provides title and description as text, plus brand and category as keyword.
Wrong: Keep match on description only; sort by price; term query on title.keyword.
Right:
- Baseline:
POST /catalog/_search with the user's current match on description; record top hits.
- Candidate:
POST /catalog/_search with:
{
"query": {
"multi_match": {
"query": "running shoes",
"fields": ["title^2", "description"],
"type": "best_fields",
"operator": "or",
"minimum_should_match": "75%"
}
},
"size": 10
}
- Compare top hits — documents with "running shoes" in
title should rank above description-only matches. If recall is
still thin, consider synonym expansion in a follow-up iteration (not sort-by-price).
Guidelines
- Ground every field name in the mapping — never invent
name, content, or body without checking
GET /{index}/_mapping.
- Query rules for pins, boosts for ranking — merchandising belongs in query rules; field boosts belong in organic
queries.
- Match criteria wiring is mandatory —
metadata keys in rule criteria must appear in the search
rule.match_criteria object with the runtime values (typically the user's query string).
- Test before claiming success — run baseline and candidate searches; cite top-hit changes.
- Keyword fields filter; text fields search — use
keyword fields in filter context, not as the primary full-text
target for natural language.
- Always deliver the concrete artifact — even when you cannot connect to a cluster to verify, produce the full
ruleset JSON (for pinning) or the candidate query body (for organic tuning), then explain how to verify once the
connection is available. Never stop at a high-level outline.
References
- Query Rules Reference — criteria types,
pinned actions, ruleset JSON, rule
query wiring, test API
- Multi-Match Tuning — field boosts, operators, testing discipline, anti-patterns
Operations
| HTTP API (shorthand) |
elastic CLI command |
GET / |
elastic es info |
GET /_cat/indices |
elastic es cat indices --index '<pattern>' |
GET /{index}/_mapping |
elastic es indices get-mapping --index '<index>' |
PUT /_query_rules/{ruleset_id} |
elastic es query-rules put-ruleset --ruleset-id '<id>' --rules '<json>' |
PUT /_query_rules/{ruleset_id}/_rule/{rule_id} |
elastic es query-rules put-rule --ruleset-id '<id>' --rule-id '<id>' --type pinned --criteria '<json>' --actions '<json>' |
GET /_query_rules/{ruleset_id} |
elastic es query-rules get-ruleset --ruleset-id '<id>' |
POST /_query_rules/{ruleset_id}/_test |
elastic es query-rules test --ruleset-id '<id>' --match-criteria '<json>' |
POST /{index}/_search |
elastic es search --index '<index>' --query '<json>' |
POST /{index}/_analyze |
elastic es indices analyze --index '<index>' --field '<field>' --text '<text>' |
1---2name: elasticsearch-search-relevance3description: Improve Elasticsearch search relevance for content and catalog indices: pin or promote results with query rules (correct rule type, criteria, and rule-query wiring) and tune organic ranking with multi_match, field boosts, and analysis grounded in the index mapping. Use when search results rank poorly, a specific document must appear first for a query, or the user asks to tune full-text matching — not for ES|QL analytics, index ingest, or cluster health.4---5
6# Elasticsearch Search Relevance
7
8Improve full-text search results on content and catalog indices. Diagnose the mapping and current query, choose the
9right relevance lever (query rules for deterministic pinning vs multi_match and field boosts for organic ranking), apply
10the change, and verify top hits before reporting success.
11
12<!-- begin-partial: preamble -->
13
14## Environment Configuration
15
16This skill executes Elasticsearch operations through the `elastic` CLI. If the
17[`elastic` CLI](https://github.com/elastic/cli#configuration) is not installed, tell the user what it is needed for. Do
18not guess credentials, call the HTTP API directly, or attempt other workarounds.
19
20This skill references operations in HTTP-shorthand form (e.g., `GET /`, `GET /_cat/indices`, `GET /{index}/_mapping`,
21`GET /{index}/_settings/index.mode`, `POST /_query`). The [Operations](#operations) table at the end of this document
22maps each shorthand to the equivalent `elastic` CLI command — always use the CLI rather than calling the HTTP API
23directly.
24
25<!-- end-partial: preamble -->
26
27## Scope
28
29This skill covers **Query DSL** relevance on indices with `text` (and optional `keyword`) fields — product catalogs,
30documentation, knowledge bases. It uses `POST /{index}/_search` for evaluation and query-rules APIs for pinned or
31excluded documents.
32
33Out of scope:
34
35- ES|QL search (`POST /_query`) — use the `elasticsearch-esql` skill.
36- Semantic / vector / hybrid retrieval — different field types and retrievers.
37- Sorting by price, date, or popularity **instead of** fixing text relevance unless the user explicitly wants
38 non-relevance ordering.
39
40## Relevance levers
41
42| User intent | Lever | APIs |
43| ------------------------------------------ | ----------------------------------------------------------- | ------------------------------------------------------------ |
44| Always show document X first for query Q | Query rules — `pinned` rule + `rule` query in search | `PUT /_query_rules/{ruleset_id}`, `POST /{index}/_search` |
45| Hide specific documents for query Q | Query rules — `exclude` rule + `rule` query | Same |
46| Better ranking for open-ended text queries | `multi_match` across mapped `text` fields with field boosts | `POST /{index}/_search` |
47| Tokens not matching user language | Operator, `minimum_should_match`, or synonym analyzers | `POST /{index}/_search`, optionally `POST /{index}/_analyze` |
48
49**Decision rule:** If the user names a document that must rank first for a specific query, use query rules. If results
50are generally weak for a phrase, tune the organic query from the mapping. Do not simulate pinning with extreme boosts,
51`function_score`, or sort clauses.
52
53## Process
54
551. **Inspect the mapping and current query.** Call `GET /` to confirm connectivity. When the index is unknown, narrow
56 candidates with `GET /_cat/indices`, then call `GET /{index}/_mapping`.
57
58 From the mapping, list every `text` field (e.g., `title`, `description`) and every `keyword` field used for filters
59 (`brand`, `category`). Note which fields are short (precision) vs long (recall). Read the user's current search body
60 if provided — identify which fields it queries and whether it already uses `rule`, `multi_match`, or single-field
61 `match`.
62
63 **Decision:** Is the problem **deterministic promotion** (one doc must win for one query) or **organic ranking**
64 (several docs should score better)? **Data needed:** index name, mapping properties, current query JSON, example
65 query strings, and target document ID(s) when pinning.
66
672. **Choose the relevance lever.** Apply the decision from step 1:
68 - **Pinning / promotion** → Create a query-rules ruleset with a rule of type `pinned` (never `exclude` for
69 promotion). Set `criteria` so the rule fires only for the intended query text — e.g., `contains` or `exact` on a
70 metadata key such as `query_string` with value `"sale"`. Set `actions` to pin the correct document via `ids` (e.g.,
71 `["SKU123"]`) or `docs` (e.g., `[{"_index":"catalog","_id":"SKU123"}]`). Use `docs` when `_id` may not be unique
72 across indices. Read [Query Rules Reference](references/query-rules-reference.md) for full structure.
73
74 - **Organic ranking** → Replace single-field `match` on a long field with `multi_match` across the mapped `text`
75 fields. Boost short fields (typically `title^2` with `description` unboosted). Consider `operator`,
76 `minimum_should_match`, or synonym-aware analyzers when multi-word recall is still poor — but do **not** sort by
77 price, date, or keyword fields to fake better text relevance, and do **not** query `.keyword` sub-fields with
78 `term` for analyzed user phrases. Read [Multi-Match Tuning](references/multi-match-tuning.md).
79
80 **Decision:** Pick exactly one primary lever per request. **Data needed:** chosen fields and boosts, ruleset ID and
81 rule ID names, criteria metadata keys, and pinned document identifiers.
82
833. **Apply the change.** Execute the APIs for the chosen lever:
84
85 **Query rules path**
86 - Create or replace the ruleset with `PUT /_query_rules/{ruleset_id}` (or add one rule with
87 `PUT /_query_rules/{ruleset_id}/_rule/{rule_id}`).
88 - Confirm structure with `GET /_query_rules/{ruleset_id}`.
89 - Validate criteria with `POST /_query_rules/{ruleset_id}/_test` using the same `match_criteria` you will pass at
90 search time.
91 - **Wire the search:** `POST /{index}/_search` must use a `rule` query whose `ruleset_id` references the ruleset and
92 whose `match_criteria` supplies values for every criteria `metadata` key (e.g., `"query_string": "sale"`). Place
93 the normal relevance clause inside `organic`. **Creating the ruleset alone does not pin anything** — the pin
94 applies only when search includes the `rule` query.
95
96 **Organic tuning path**
97 - Build a candidate `multi_match` (or equivalent bool/should) query from the mapping.
98 - Optionally inspect analysis with `POST /{index}/_analyze` on sample query text when tokenization explains misses.
99
100 **Decision:** Stop after one coherent change set; avoid stacking unrelated edits before testing.
101
1024. **Test and compare top hits.** Before and after each candidate, call `POST /{index}/_search` with the same `size` (≥
103 10), the user's query string, and `"track_scores": true`. For pinning, the search body **must** include the `rule`
104 query from step 3.
105
106 Compare for each run:
107 - Top `_id` values and order
108 - `_score` where relevant
109 - Key `_source` fields (`title`, `description`, product id)
110
111 For pinning, confirm the target document (e.g., `SKU123`) is **first** when `match_criteria` matches the query and
112 that organic matches still appear below. For organic tuning, confirm titles and intent-aligned documents rise without
113 relying on sort or keyword exact-match hacks.
114
115 **Decision:** Ship the candidate that wins on evidence; if none improve results, report what was tried and propose
116 the next lever (e.g., synonyms or additional fields). **Data needed:** side-by-side top-hit lists from baseline and
117 candidate queries.
118
119## Examples
120
121### Pin SKU123 for query "sale" on `catalog`
122
123**Wrong:** Boost `SKU123`, sort by `_id`, or create a ruleset without a `rule` search query.
124
125**Right:**
126
1271. `PUT /_query_rules/catalog-sale-pin` with a `pinned` rule, criteria matching query text `"sale"`, actions pinning
128 `SKU123`.
1292. `POST /catalog/_search` with:
130
131```json
132{
133 "query": {
134 "rule": {
135 "ruleset_id": "catalog-sale-pin",
136 "match_criteria": { "query_string": "sale" },
137 "organic": {
138 "multi_match": {
139 "query": "sale",
140 "fields": ["title^2", "description"]
141 }
142 }
143 }
144 },
145 "size": 10
146}
147```
148
149Verify `SKU123` is hit #1 and remaining hits are organic matches below the pin.
150
151### Improve "running shoes" when only `description` is searched
152
153Mapping provides `title` and `description` as `text`, plus `brand` and `category` as `keyword`.
154
155**Wrong:** Keep `match` on `description` only; sort by price; `term` query on `title.keyword`.
156
157**Right:**
158
1591. Baseline: `POST /catalog/_search` with the user's current `match` on `description`; record top hits.
1602. Candidate: `POST /catalog/_search` with:
161
162```json
163{
164 "query": {
165 "multi_match": {
166 "query": "running shoes",
167 "fields": ["title^2", "description"],
168 "type": "best_fields",
169 "operator": "or",
170 "minimum_should_match": "75%"
171 }
172 },
173 "size": 10
174}
175```
176
1771. Compare top hits — documents with "running shoes" in `title` should rank above description-only matches. If recall is
178 still thin, consider synonym expansion in a follow-up iteration (not sort-by-price).
179
180## Guidelines
181
182- **Ground every field name in the mapping** — never invent `name`, `content`, or `body` without checking
183 `GET /{index}/_mapping`.
184- **Query rules for pins, boosts for ranking** — merchandising belongs in query rules; field boosts belong in organic
185 queries.
186- **Match criteria wiring is mandatory** — `metadata` keys in rule criteria must appear in the search
187 `rule.match_criteria` object with the runtime values (typically the user's query string).
188- **Test before claiming success** — run baseline and candidate searches; cite top-hit changes.
189- **Keyword fields filter; text fields search** — use `keyword` fields in `filter` context, not as the primary full-text
190 target for natural language.
191- **Always deliver the concrete artifact** — even when you cannot connect to a cluster to verify, produce the full
192 ruleset JSON (for pinning) or the candidate query body (for organic tuning), then explain how to verify once the
193 connection is available. Never stop at a high-level outline.
194
195## References
196
197- [Query Rules Reference](references/query-rules-reference.md) — criteria types, `pinned` actions, ruleset JSON, `rule`
198 query wiring, test API
199- [Multi-Match Tuning](references/multi-match-tuning.md) — field boosts, operators, testing discipline, anti-patterns
200
201## Operations
202
203| HTTP API (shorthand) | `elastic` CLI command |
204| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
205| `GET /` | `elastic es info` |
206| `GET /_cat/indices` | `elastic es cat indices --index '<pattern>'` |
207| `GET /{index}/_mapping` | `elastic es indices get-mapping --index '<index>'` |
208| `PUT /_query_rules/{ruleset_id}` | `elastic es query-rules put-ruleset --ruleset-id '<id>' --rules '<json>'` |
209| `PUT /_query_rules/{ruleset_id}/_rule/{rule_id}` | `elastic es query-rules put-rule --ruleset-id '<id>' --rule-id '<id>' --type pinned --criteria '<json>' --actions '<json>'` |
210| `GET /_query_rules/{ruleset_id}` | `elastic es query-rules get-ruleset --ruleset-id '<id>'` |
211| `POST /_query_rules/{ruleset_id}/_test` | `elastic es query-rules test --ruleset-id '<id>' --match-criteria '<json>'` |
212| `POST /{index}/_search` | `elastic es search --index '<index>' --query '<json>'` |
213| `POST /{index}/_analyze` | `elastic es indices analyze --index '<index>' --field '<field>' --text '<text>'` |