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.

SettingMeaningDefault
fragment_sizeMaximum characters per fragment100
number_of_fragmentsMaximum fragments returned per field5
no_match_sizeCharacters returned when the field has no match0
"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 &lt;script&gt; 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

TypeCharacteristics
unifiedDefault, fast, splits text into sentences, works for most cases
plainReanalyzes 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.