ES|QL HIGHLIGHT command
The HIGHLIGHT processing command
extracts and highlights matching text snippets from one or more fields based on a
full-text query. Matching terms are wrapped in highlight tags, bringing the
highlighting features of the Elasticsearch
_search API to ES|QL.
HIGHLIGHT [prefix = "<prefix>"] query ON field [, field, ...] [WITH { "option": value [, ...] }]
prefix- (Optional) A quoted string literal used to name the output columns. Each
highlighted field is written to
<prefix><field>. Defaults tohighlight_(for example,HIGHLIGHT "fox" ON contentproduceshighlight_content). If a generated column name matches an existing column, the existing column is replaced. To overwrite the source column in place, specify an empty prefix (prefix = ""). Unlike the query and theWITHoption values,prefixcannot be a query parameter. query-
The query used to find matching terms to highlight. This can be a string literal (which uses
query_stringsyntax) or a full-text search function such asMATCH,MATCH_PHRASE,QSTR,KQL, or the match operator:. You can combine full-text functions usingAND,OR, andNOT. Unqualified strings andQSTRexpressions are evaluated against all fields listed inON. All fields referenced in the query must also be listed inON. If an unlisted field is referenced, the query is rejected before execution (for example,HIGHLIGHT query field [title] is not in ON fields [body]).Queries without positive match conditions (such as
NOT MATCH(...)) have no terms to highlight and returnnull, unlessno_match_sizeis configured. field- One or more comma-separated columns to highlight. Fields must be
textorkeywordtypes (semantic_textfields are supported and treated astext). Wildcard column names are not supported. If a field has no matching terms, its output isnullunless you setno_match_size.
All option values passed in the WITH clause must be constants. Both literals and
query parameters that
resolve to a literal are accepted; column references are not.
pre_tags- (Optional) Opening tag inserted before each highlighted term. Accepts a string
or a single-element array of strings. Defaults to
<em>. Multiple rotating tags are not supported. post_tags- (Optional) Closing tag inserted after each highlighted term. Accepts a string
or a single-element array of strings. Defaults to
</em>. encoder- (Optional) Text encoding applied before adding highlight tags. Accepts
default(no encoding) orhtml(HTML-escapes snippet text). Defaults todefault. As in the_searchAPI, this value is case-sensitive, sohtmlis valid butHTMLis rejected.boundary_scannerandorderare case-insensitive. analyzer- (Optional) Analyzer used on both the query and field text. Defaults to the
standardanalyzer. Only built-in and node-level plugin analyzers are supported. If a full-text search function specifies its ownanalyzer, it must match the analyzer specified here. number_of_fragments- (Optional) Maximum number of snippets (fragments) to return per field. Set to
0to return the entire field value with matching terms highlighted without fragmenting. Must be>= 0. Defaults to5. fragment_size- (Optional) Approximate character length of each snippet. Must be
>= 0. Defaults to100. no_match_size- (Optional) Approximate number of leading characters to return from the field
when there are no matching terms. This is a minimum, not an exact limit: the
returned text extends to the next boundary set by
boundary_scanner, so the result can be longer than the requested size. Must be>= 0. Defaults to0(returnsnull). boundary_scanner- (Optional) Boundary scanner used to split text into fragments. Accepts
sentenceorword, case-insensitively. Defaults tosentence. boundary_scanner_locale- (Optional) Locale used by the boundary scanner, given as an
IETF BCP 47 language tag such as
en-USorja-JP. Use hyphens as separators. Defaults to the root locale. This is the same format accepted by the_searchAPI'sboundary_scanner_locale. order- (Optional) Sort order of returned fragments. Accepts
none(preserves document order) orscore(orders fragments by descending relevance score), case-insensitively. Defaults tonone. max_analyzed_offset- (Optional) Maximum number of characters to analyze per field value. Accepts a
positive integer, or
-1to leave the limit unset. Defaults to-1.HIGHLIGHTanalyzes at most 1 million characters per field value regardless of this setting, and the index'sindex.highlight.max_analyzed_offsetsetting does not apply. Text beyond the effective offset is not highlighted.
Use HIGHLIGHT to find and display matching snippets in text fields, typically
after filtering rows with a full-text search condition in WHERE.
HIGHLIGHT processes each row, analyzes the specified fields against the
query, and generates new keyword columns containing matching terms wrapped in
highlight tags. By default, output columns are named highlight_<field>. If a
field contains no matching terms, the result is null unless you specify
no_match_size.
Because HIGHLIGHT re-analyzes text values at query time, you can highlight
source fields from an index as well as computed columns created by earlier
commands like EVAL, DISSECT, GROK, STATS, ENRICH, or LOOKUP JOIN.
For multivalued fields, each value is highlighted independently:
- Phrase queries and fragment boundaries do not cross values.
- When a field produces multiple fragments, the output column contains a multivalued list of snippets.
- Multivalued
keywordfields loaded from doc values are sorted and deduplicated before highlighting, which can result in a different snippet order compared to the_searchAPI.
Learn more about using ES|QL for search use cases.
HIGHLIGHTre-analyzes text with thestandardanalyzer by default, rather than the analyzer configured in the index mapping. If your field uses a custom or language analyzer, specify it with theanalyzeroption in theWITHclause.- The
analyzeroption only supports built-in and node-level plugin analyzers. Analyzers configured in index settings are not supported. - On
keywordfields,HIGHLIGHTtokenizes text and breaks it into snippets like a text field, rather than treating the value as a single term. - On
semantic_textfields,HIGHLIGHTperforms lexical matching against the underlying text. Semantic vector matches without literal keyword overlap are not highlighted. - Fields are analyzed up to a maximum of 1 million characters. Text beyond this limit is not analyzed or highlighted.
The following examples show common ways to highlight search terms and customize snippet output.
Wrap matching terms in the default <em> tags:
ROW content = "The quick brown fox jumps over the lazy dog."
| HIGHLIGHT "fox" ON content
| KEEP highlight_content
| highlight_content:keyword |
|---|
| The quick brown <em>fox</em> jumps over the lazy dog. |
Filter rows with a WHERE clause, then highlight the matching terms in the output:
FROM books
| WHERE MATCH(title, "Return")
| HIGHLIGHT "return" ON title
| KEEP book_no, highlight_title
| SORT book_no
| book_no:keyword | highlight_title:keyword |
|---|---|
| 2714 | <em>Return</em> of the King Being the Third Part of The Lord of the Rings |
| 7350 | <em>Return</em> of the Shadow |
Use a full-text function like MATCH_PHRASE to highlight an exact phrase in a single tag pair:
FROM books
| WHERE MATCH(title, "Return")
| HIGHLIGHT MATCH_PHRASE(title, "Return of the") ON title
| KEEP book_no, highlight_title
| SORT book_no
| book_no:keyword | highlight_title:keyword |
|---|---|
| 2714 | <em>Return of the</em> King Being the Third Part of The Lord of the Rings |
| 7350 | <em>Return of the</em> Shadow |
Use QSTR to highlight terms using Lucene query syntax with boolean operators and field qualifiers:
ROW title = "The quick fox", body = "A loyal dog"
| HIGHLIGHT QSTR("title:fox OR body:dog") ON title, body
| KEEP highlight_title, highlight_body
| highlight_title:keyword | highlight_body:keyword |
|---|---|
| The quick <em>fox</em> | A loyal <em>dog</em> |
Use KQL to highlight terms using Kibana Query Language syntax, optionally combined with other full-text functions:
FROM books
| WHERE MATCH(title, "Return")
| HIGHLIGHT KQL("title: shad*") OR (MATCH(title, "return") AND MATCH(title, "king")) ON title
| KEEP book_no, highlight_title
| SORT book_no
| book_no:keyword | highlight_title:keyword |
|---|---|
| 2714 | <em>Return</em> of the <em>King</em> Being the Third Part of The Lord of the Rings |
| 7350 | Return of the <em>Shadow</em> |
Use the analyzer option to apply language-specific stemming rules. In this example, the english analyzer stems Rings to ring:
ROW title = "The Lord of the Rings"
| HIGHLIGHT "ring" ON title WITH { "analyzer": "english" }
| KEEP highlight_title
| highlight_title:keyword |
|---|
| The Lord of the <em>Rings</em> |
Highlight multiple columns at once by listing them in ON:
ROW title = "Return of the King", body = "Tolkien wrote the epic saga."
| HIGHLIGHT "king tolkien" ON title, body
| KEEP highlight_title, highlight_body
| highlight_title:keyword | highlight_body:keyword |
|---|---|
| Return of the <em>King</em> | <em>Tolkien</em> wrote the epic saga. |
HIGHLIGHT re-analyzes field values at query time, so it works on columns created earlier in the pipeline:
ROW raw = "2024 Sauron Mordor"
| DISSECT raw "%{yr} %{name} %{place}"
| HIGHLIGHT "sauron" ON name
| KEEP name, highlight_name
| name:keyword | highlight_name:keyword |
|---|---|
| Sauron | <em>Sauron</em> |
Use "encoder": "html" to escape HTML tags and special characters in the text while keeping the highlight tags intact:
ROW content = "Use <b>bold</b> tags & special chars with the Ring."
| HIGHLIGHT "ring" ON content WITH { "encoder": "html" }
| KEEP highlight_content
| highlight_content:keyword |
|---|
| Use <b>bold</b> tags & special chars with the <em>Ring</em>. |
Set "number_of_fragments": 0 to return the complete text value with matches highlighted rather than returning individual snippets:
ROW content = "Elasticsearch is fast. Elasticsearch is scalable. Elasticsearch is open."
| HIGHLIGHT "elasticsearch" ON content WITH { "number_of_fragments": 0 }
| KEEP highlight_content
| highlight_content:keyword |
|---|
| <em>Elasticsearch</em> is fast. <em>Elasticsearch</em> is scalable. <em>Elasticsearch</em> is open. |
Use pre_tags and post_tags to specify custom wrapping tags:
ROW content = "The quick brown fox jumps over the lazy dog."
| HIGHLIGHT "fox" ON content WITH { "pre_tags": ["<b>"], "post_tags": ["</b>"] }
| KEEP highlight_content
| highlight_content:keyword |
|---|
| The quick brown <b>fox</b> jumps over the lazy dog. |
Use prefix to change the column name prefix:
ROW content = "The One Ring was forged by Sauron."
| HIGHLIGHT prefix = "hl_" "ring" ON content
| KEEP content, hl_content
| content:keyword | hl_content:keyword |
|---|---|
| The One Ring was forged by Sauron. | The One <em>Ring</em> was forged by Sauron. |
Set an empty prefix (prefix = "") to replace the source column with the highlighted output:
ROW content = "The quick brown fox jumps over the lazy dog."
| HIGHLIGHT prefix = "" "fox" ON content
| KEEP content
| content:keyword |
|---|
| The quick brown <em>fox</em> jumps over the lazy dog. |
By default, non-matching fields evaluate to null. Set no_match_size to return text from the start of the field instead:
ROW content = "Gardens and flowers bloom in spring."
| HIGHLIGHT "elasticsearch" ON content WITH { "no_match_size": 200 }
| KEEP highlight_content
| highlight_content:keyword |
|---|
| Gardens and flowers bloom in spring. |
Use "order": "score" to sort snippets by relevance score rather than document order:
ROW content = ["fast search", "fast and fast results"]
| HIGHLIGHT "fast" ON content WITH { "order": "score" }
| KEEP highlight_content
| highlight_content:keyword |
|---|
| [<em>fast</em> and <em>fast</em> results, <em>fast</em> search] |