Elasticsearch Highlighting
A search results page works best when it shows why each result matched. Web search engines bold the searched words inside short text snippets, and visitors scan those snippets to pick the right link. Elasticsearch offers the same feature through highlighting. The response carries text fragments with the matching words wrapped in tags of your choice.
Query: "solar panel"
Result without highlighting:
Title: Home Energy Guide
(visitor sees only the title)
Result with highlighting:
Title: Home Energy Guide
"...install a <em>solar</em> <em>panel</em> on the south-facing roof..."
A Basic Highlight Request
Add a highlight section beside the query. List the fields to highlight.
GET /articles/_search
{
"query": { "match": { "body": "solar panel" } },
"highlight": {
"fields": {
"body": {}
}
}
}
Each hit in the response gains a "highlight" object. It holds a list of fragments for every highlighted field.
"highlight": {
"body": [
"Install a <em>solar</em> <em>panel</em> on the south-facing roof",
"Each <em>panel</em> produces power from <em>solar</em> energy"
]
}
Custom Tags
The default tag is em. Choose other tags to match your page design with pre_tags and post_tags.
"highlight": {
"pre_tags": ["<mark>"],
"post_tags": ["</mark>"],
"fields": { "body": {} }
}
Controlling Fragment Size and Count
Long documents would produce huge snippets without limits. Two settings keep them short.
| Setting | Meaning | Default |
|---|---|---|
| fragment_size | Maximum characters per fragment | 100 |
| number_of_fragments | Maximum fragments returned per field | 5 |
| no_match_size | Characters returned when the field has no match | 0 |
"highlight": {
"fields": {
"body": { "fragment_size": 150, "number_of_fragments": 2 },
"title": { "number_of_fragments": 0 }
}
}
Setting number_of_fragments to 0 returns the entire field with matches marked. Titles and short fields work well with this setting.
Highlighting Several Fields
List multiple fields to mark matches in titles, summaries, and bodies together. A multi_match query searches across the same fields.
GET /articles/_search
{
"query": {
"multi_match": {
"query": "solar panel",
"fields": ["title", "summary", "body"]
}
},
"highlight": {
"fields": { "title": {}, "summary": {}, "body": {} }
}
}
Protecting Your Page from Unsafe Markup
Stored text can contain HTML or script tags. Raw fragments inserted into a web page could run unwanted code. Set the encoder to html, and Elasticsearch escapes unsafe characters in the fragment before adding the highlight tags.
"highlight": {
"encoder": "html",
"fields": { "body": {} }
}
Stored text: Use the <script> tag carefully
With encoder html: Use the <script> tag carefully
(displays as text, never runs)
Always enable the html encoder when a page displays fragments from user-generated content.
Matching Fields Strictly
The require_field_match setting decides which terms a field highlights. When true, a field highlights only terms that matched through a query clause aimed at that field. When false, a field highlights any query term it contains. The default is true, which keeps snippets accurate for most pages.
Highlighter Types
| Type | Characteristics |
|---|---|
| unified | Default, fast, splits text into sentences, works for most cases |
| plain | Reanalyzes text on the fly, works without extra index data, slower on large fields |
| fvh (fast vector) | Needs term vectors stored in the mapping, handles very large fields, supports advanced options |
Performance Tips
Highlighting reads the stored text of every returned hit. Return only ten to twenty hits per page. Keep fragment counts low. Avoid highlighting fields that hold megabytes of text, or use the fvh type for them. Skip the feature entirely for API calls that never display snippets.
Highlighting a Complete Search Page
A typical results page combines a query, a filter, source filtering, and highlighting in a single request. The page shows the title, the author, and a short snippet with matching words in bold.
GET /articles/_search
{
"_source": ["title", "author"],
"size": 10,
"query": {
"bool": {
"must": { "match": { "body": "solar panel" } },
"filter": { "term": { "status": "published" } }
}
},
"highlight": {
"pre_tags": ["<mark>"],
"post_tags": ["</mark>"],
"encoder": "html",
"fields": {
"body": { "fragment_size": 140, "number_of_fragments": 2 }
}
}
}
The request returns only the fields the page needs plus safe, short snippets. Source filtering keeps the large body field out of the response, while highlighting still reads it on the server. Elasticsearch fetches the stored text for highlighting independently of the returned fields.
Test highlighting with queries that include stemmed words. A search for "running" highlights "run" and "runs" when the analyzer reduces words to stems. Visitors see the variants that matched, which explains the result.
