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, ...] | ON *] [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-
(Optional) 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.If you don't specify a query,
HIGHLIGHTautomatically reuses full-text search conditions from earlierWHEREcommands in the query. Refer to Reuse a query from WHERE.When you provide both a query and an
ONclause, any field named in your query must also be listed inON. For example,HIGHLIGHT MATCH(title, "fox") ON bodyis rejected becausetitleis not inON. When you let ES|QL determine the query or fields automatically, it handles this check for you.Unqualified string literals and
QSTRexpressions are evaluated against whichever fields are being highlighted. Queries without positive search conditions (such asNOT MATCH(...)) have no terms to highlight and returnnull, unless you configureno_match_size. field-
(Optional) One or more comma-separated columns to highlight, or
*to highlight everytextandkeywordcolumn in the table. Fields must betextorkeywordtypes (semantic_textfields are supported and treated astext). You can only use*by itself; wildcard patterns liketitle*and combining*with specific field names (such asON *, title) are not supported.If you omit
ON,HIGHLIGHTdetermines which columns to highlight based on your query:- For queries targeting a specific column (such as
MATCHorMATCH_PHRASE), only that column is highlighted. - For queries that don't target a single column (such as string literals,
QSTR, orKQL),HIGHLIGHTchecks alltextandkeywordcolumns in the table.
Refer to Choose fields with ON. If a field has no matching terms, its output is
nullunless you setno_match_size. - For queries targeting a specific column (such as
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 text 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.
Most search queries filter rows with a full-text condition in WHERE, then
highlight matching terms in those same fields. To avoid repeating your search
query, you can omit the query from HIGHLIGHT. When you do, HIGHLIGHT
automatically finds and reuses full-text search conditions from earlier
WHERE commands.
This works with any positive full-text search function, including
MATCH,
MATCH_PHRASE,
QSTR,
KQL,
and the match operator :.
You can include intermediate commands between WHERE and HIGHLIGHT as long as
each row still represents an individual document. For example, commands like
KEEP, DROP, RENAME, EVAL, GROK, DISSECT, LIMIT, SORT,
MV_EXPAND, and INLINE STATS pass through without issue.
However, commands that summarize, aggregate, or join rows—such as STATS,
LOOKUP JOIN, or FORK—change the document context. If you use any of these
commands between WHERE and HIGHLIGHT, you must provide the query explicitly
in HIGHLIGHT.
If your query contains multiple WHERE clauses, HIGHLIGHT combines all of
their full-text search conditions so that every searched field can produce
snippets, even though the WHERE clauses filter your rows together using AND.
The following search conditions cannot be automatically reused:
- Negated conditions, such as
NOT MATCH(...)(there are no positive matches to highlight) - Conditions combined with non-text filters using
OR, such asMATCH(title, "fox") OR year > 2020
If your query relies solely on conditions that cannot be reused, specify the
query explicitly in HIGHLIGHT.
If you provide an explicit query in HIGHLIGHT, it takes precedence, and any
conditions from earlier WHERE commands are ignored for highlighting.
The ON clause specifies which columns to highlight. You can choose specific
columns, highlight all available text columns, or let ES|QL determine the
columns automatically:
- Highlight specific fields: Use
ON field1, field2to highlight only the specified columns. - Highlight all text and keyword fields: Use
ON *to highlight everytextandkeywordcolumn in the current table, including multi-fields (such asauthor.keyword) andsemantic_textfields (highlighted lexically). Metadata columns such as_idand_indexare not included. - Let ES|QL determine fields: If you omit
ON,HIGHLIGHTchooses the columns based on your query:- If the query targets a specific field (such as
MATCH(title, "fox")), only that field is highlighted. - If the query does not name a specific field (such as a string literal,
QSTR, orKQL),HIGHLIGHTchecks alltextandkeywordcolumns in the table.
- If the query targets a specific field (such as
If a highlighted field does not match any query terms, its output is null
(or the leading text specified by no_match_size). If ES|QL cannot find any
eligible text or keyword columns to highlight, you must provide an explicit
ON clause.
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.
HIGHLIGHTcannot automatically reuse aWHEREquery across commands that aggregate, summarize, or join rows, such asSTATS,LOOKUP JOIN, orFORK. In those queries, specify the query directly onHIGHLIGHT.- If you drop a field targeted by the reused
WHEREquery beforeHIGHLIGHT, the implicit query can no longer highlight that field. If no other reusable fields remain, provide an explicit query andONclause using columns that are still in scope.
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 matching terms in the output.
You can specify the search condition again in HIGHLIGHT:
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 |
To avoid repeating your search query, omit the query from HIGHLIGHT. When you
also omit ON, HIGHLIGHT automatically highlights matches in the field
searched by WHERE (in this case, creating highlight_title):
FROM books
| WHERE MATCH(title, "Return")
| HIGHLIGHT
| KEEP title, highlight_title
| SORT title
| title:text | highlight_title:keyword |
|---|---|
| Return of the King Being the Third Part of The Lord of the Rings | <em>Return</em> of the King Being the Third Part of The Lord of the Rings |
| Return of the Shadow | <em>Return</em> of the Shadow |
To reuse the WHERE condition but choose which columns to highlight, provide
an explicit ON clause:
FROM books
| WHERE MATCH(title, "Return")
| HIGHLIGHT 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 |
When your query targets a specific field (such as MATCH), you can omit ON.
Only that field is highlighted, leaving other columns untouched:
ROW title = "Return of the King", body = "An ordinary description."
| HIGHLIGHT MATCH(title, "king")
| title:keyword | body:keyword | highlight_title:keyword |
|---|---|---|
| Return of the King | An ordinary description. | Return of the <em>King</em> |
When you use a query that doesn't target a specific field, such as a string
literal or QSTR, omitting ON highlights all text and keyword columns:
ROW title = "Return of the King", body = "Tolkien wrote the epic saga."
| HIGHLIGHT "tolkien"
| KEEP highlight_title, highlight_body
| highlight_title:keyword | highlight_body:keyword |
|---|---|
| null | <em>Tolkien</em> wrote the epic saga. |
Use ON * to highlight every text and keyword column in the table at once.
Columns that do not match the query evaluate to null:
ROW title = "Return of the King", body = "An ordinary description."
| HIGHLIGHT MATCH(title, "king") ON *
| KEEP highlight_title, highlight_body
| highlight_title:keyword | highlight_body:keyword |
|---|---|
| Return of the <em>King</em> | null |
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] |