Define reusable queries with ES|QL views
A view is a virtual index defined by an ES|QL query. You reference a view by name in the FROM command, just like an ordinary index. The query runs each time the view is referenced, so results always reflect the current state of the data.
A view has two components:
- Name: unique within the index namespace, used anywhere an index name is accepted in
FROM. - Definition: a complete ES|QL query that runs each time the view is referenced.
Here's how a view works in practice:
-
Start with a query
FROM addresses | RENAME city.country.name AS country | EVAL country = CASE(country == "United States of America", "United States", country) | STATS count=COUNT() BY countrycount:long country:keyword 1 Japan 1 Netherlands 1 United States -
Save it as a view
PUT /_query/view/country_addresses{ "query": """ FROM addresses | RENAME city.country.name AS country | EVAL country = CASE(country == "United States of America", "United States", country) | STATS count=COUNT() BY country """ } -
Reference it by name, just like an index
FROM country_addressescount:long country:keyword 1 Japan 1 Netherlands 1 United States
Views are a good fit when you want to:
- Reuse a named query. Wrap a frequently used ES|QL pipeline as a view and reference it by name, instead of repeating the same query in every request.
- Abstract common transformations. Centralize renames, type conversions, or derived fields so consumers see a consistent set of columns without needing to know the underlying source structure.
- Combine pre-processed data sources. Define one view per source, each with its own filters or aggregations, and query them together in a single
FROMclause. - Simplify queries for downstream tools. Dashboards, alerts, or ad-hoc analysts can query
FROM my_viewwithout needing to know the indices or processing commands behind it.
Use the REST API to create, update, delete, and list views:
PUT /_query/view/<name> rejects a query that cannot be parsed.
validate query parameter to true. Validation is off by default.
With validate=true:
- If the query uses an index or column that does not exist, the response carries a
Warningheader. - A warning does not stop Elasticsearch from saving the view. The view is saved whether or not validation reports a problem, so you can create a view before the index it reads from.
- The analysis uses your privileges. An index you cannot read is reported as unknown.
- Failures unrelated to the query, such as an overloaded cluster, produce no warning.
Some views are reserved. Elasticsearch or its components create them, not users. A reserved view is marked with "reserved": true in the get view response.
You cannot modify or delete a reserved view through the REST API:
PUT /_query/view/<name>fails if a reserved view with that name exists. You also cannot setreservedin a request.DELETE /_query/view/<name>fails withcannot delete reserved view [<name>].
You can query and read a reserved view like any other view, subject to the same privileges.
Use views as if they were ordinary indices:
FROM index_pattern
Where index_pattern is a comma-separated list of index or view names. Index
expressions can include wildcards and date math. Whether a wildcard also matches
views depends on the version and deployment type.
By default, a wildcard pattern in FROM does not match views. FROM my-view-1
reads the view by exact name, while FROM my-view-* resolves to indices, data
streams, and aliases only. Registered views are excluded.
To include views in wildcard resolution, enable the wildcards_match_views setting:
SET wildcards_match_views = true;
FROM my-view-*
You can also send it in the _query request body as "settings": {"wildcards_match_views": true}. A value set in the query overrides the request body, but neither value enters a referenced view. Refer to Settings across view boundaries for the limits and warnings.
esql.query.settings.wildcards_match_views in elasticsearch.yml or
through the cluster settings API. The cluster default applies both outside and
inside views.
Wildcard patterns in FROM match registered views automatically.
View operations use the standard Elasticsearch index privileges, applied to the view name.
| Operation | Privilege (on the view name) |
|---|---|
Query (FROM my_view) |
read, all |
Create or update (PUT /_query/view/<name>) |
create_view, manage_view, manage, all |
Read definition (GET /_query/view/<name>) |
read_view_metadata, manage_view, manage, all |
Delete (DELETE /_query/view/<name>) |
delete_view, manage_view, manage, all |
read on a view name permits querying the view but does not grant access to the data behind it. The caller must also have read on every index or alias the view definition resolves to, including through nested views.
Access is enforced at query time. Creating a view requires no privilege over the indices it references.
If some underlying indices are unauthorized, the query fails with a 403. If none are accessible, it fails with a 400 Unknown index. Wildcard patterns (FROM view-*) silently exclude unauthorized views.
Nested views are resolved under the calling user's credentials. Access to an outer view does not grant access to any inner view it references.
read on a view name must not carry DLS or FLS. If it does, the query fails with a 403 and the views_with_dls_or_fls error field lists the affected names. This applies at every nesting level.
DLS or FLS on the underlying indices is applied normally.
{
"indices": [
{
"names": ["country_addresses"],
"privileges": ["read", "create_view", "read_view_metadata", "delete_view"]
},
{
"names": ["addresses"],
"privileges": ["read"]
}
]
}
The following examples show how to use views within the FROM command.
Assume we've defined three views in a similar way to the example above, each counting the number of documents that reference a particular country, but from three different source indices:
country_airports- reports counts of documents per country from ourairportsindexcountry_addresses- reports counts of documents per country from ouraddressesindexcountry_languages- reports counts of documents per country from ourlanguagesindex
Now we can query these together with a query like:
FROM country_addresses, country_airports, country_languages
| WHERE country LIKE "United*"
| SORT country ASC, count DESC
| count:long | country:keyword |
|---|---|
| 17 | United Kingdom |
| 1 | United Kingdom |
| 129 | United States |
| 1 | United States |
| 1 | United States |
The same country might appear in multiple views, producing multiple rows.
We could combine these with a STATS command, using SUM(count) BY country.
This example requires wildcards_match_views = true. By default, wildcards do
not match views. Refer to wildcard patterns and views
and review the ES|QL view limitations.
FROM country_*
| STATS count=SUM(count) BY country
| WHERE count > 11
| SORT count DESC, country ASC
| count:long | country:keyword |
|---|---|
| 131 | United States |
| 50 | India |
| 45 | Mexico |
| 41 | China |
| 38 | Canada |
| 31 | Brazil |
| 26 | Russia |
| 18 | United Kingdom |
| 17 | Australia |
| 13 | Argentina |
| 13 | Germany |
| 12 | France |
| 12 | Indonesia |
Note how we used SUM to combine the counts of the three previously aggregated count columns.
We can define views with complex queries, including commands like LOOKUP JOIN:
PUT /_query/view/airports_mp_filtered
{
"query": """
FROM airports
| RENAME abbrev AS code
| LOOKUP JOIN airports_mp ON abbrev == code
| WHERE abbrev IS NOT NULL
| DROP code
"""
}
This creates a view called airports_mp_filtered that contains all rows from the airports index that also have a matching abbrev inside the airports_mp index.
This is effectively a subset of the airports index.
We could, for example, see how many airports are defined only in airports versus how many are defined in the view, by combining both a view and an index in the same FROM command:
FROM airports_mp_filtered, airports
| STATS duplications=COUNT() BY abbrev
| STATS count=COUNT() BY duplications
| SORT count DESC
| count:long | duplications:long |
|---|---|
| 880 | 1 |
| 7 | 2 |
| 1 | 3 |
The METADATA directive is supported both inside and outside a view.
Views follow the same rules as subqueries.
Inside the view, the directive generates columns just like other fields. You can use them for filtering and as output columns.
Except for _class and _name, an outer METADATA request returns null when the source output does not contain the requested field.
When the source retains a metadata field in its output, the outer query receives its values even if it does not request that field with METADATA.
METADATA on the outer FROM is not handled consistently.
If the FROM names only a view, the directive is ignored. For example, FROM my_view METADATA _index returns the view contents without an _index column.
If the FROM combines that source with other sources, the requested metadata columns might appear with null values for rows from that source, but this behavior is not consistent.
To retrieve metadata values reliably, request the fields inside the source and retain them in its output.
_class and _name are the only metadata fields that work on a view without the view body declaring them. The outer query answers them for each view. _class is view. _name is the view name. You do not need the directive inside the view. If the view body also declares _class or _name, the outer values replace them.
FROM airports_mp_filtered, airports METADATA _class, _name
| WHERE abbrev == "LUH"
| KEEP abbrev, _class, _name
| SORT _name
| abbrev:keyword | _class:keyword | _name:keyword |
|---|---|---|
| LUH | index | airports |
| LUH | view | airports_mp_filtered |
Views behave like inline subqueries at execution time and when you start combining multiple views, it helps to know how nesting works and where the limits are.
When a query references one or more views, each view's definition query executes independently at query time, in parallel where possible. This is the same execution model used by FROM subqueries and FORK.
Results from all sources (indices, views, subqueries) are unioned into a single result set. Duplicate rows are preserved. Columns that exist in one source but not another are filled with null.
A view definition can reference another view. This is called a nested view. ES|QL allows nesting to a depth of 10.
When multiple views are referenced within the same index pattern, each view executes independently (in parallel if possible), similar to subqueries and FORK.
Query compaction flattens nested view branches into one merge only when the result has at most 20 branches by default (esql.query.max_branch_count). If flattening would exceed that cap, ES|QL keeps the nested plan instead of failing the query. The same setting also caps the total number of leaf branches in the query.
A query and the views and subqueries it references are limited to 500 commands in total (esql.query.max_command_count). The limit applies both to the query as written and to the expanded plan after every view and subquery body is inlined, so deeply nested or heavily repeated view references fail instead of consuming unbounded memory.
Views, subqueries, and FORK share a maximum branch count of 8. For example, a single index pattern could reference four views and four subqueries, but adding one more would exceed the limit and the query will fail.
Nested branching is supported. A view definition can contain FROM subqueries,
nested views, and FORK, and a query can combine that view with other
branched sources. The complete expanded query remains subject to the branch and
command limits described above. The restriction on two user-written FORK
commands along the same uninterrupted pipeline still applies.
Branching and nesting are allowed in combination only when there is no more than one branch point:
- A view can contain subqueries, but that view cannot be used together with other views, and the subqueries can reference only nested views that contain no further branching.
- A subquery can contain views, but those views must not introduce any additional
branch points through subqueries or
FORK.
When a view definition contains branches, such as subqueries or references to other views, query compaction tries to flatten the inner branches into the outer branch set.
Compaction produces a single-level plan when the flattened width is within
esql.query.max_branch_count (20 by default). If flattening would exceed the
limit, ES|QL keeps the nested plan.
Compaction produces the single branch point these versions require when the flattened plan has at most 8 branches. Otherwise, the query fails.
The following example shows how compaction works. Two views are each defined as a pair of subqueries:
PUT /_query/view/view_x
{
"query": """
FROM (
FROM app-events-* | KEEP msg, level
), (
FROM auth-events-* | KEEP msg, level
)
"""
}
PUT /_query/view/view_y
{
"query": """
FROM (
FROM nginx-events-* | KEEP msg, level
), (
FROM apache-events-* | KEEP msg, level
)
"""
}
A query references both views alongside a regular index:
FROM other-events, view_x, view_y
| STATS count(msg) BY level
Without compaction, this would create two levels of branching. Three outer branches exist, and two of them branch again inside their view definitions:
Compaction flattens the inner view branches into the outer branch set, producing a single-level plan with five branches:
Compaction doesn't apply if the view definition contains processing commands after its subqueries. Those commands must run on the combined branch output, so the branch level can't be collapsed.
If compaction isn't possible, or flattening would exceed the branch cap, ES|QL keeps the nested plan and executes it subject to the overall branch and command limits.
If compaction isn't possible, the query fails because these versions allow only one branch point in the plan.
ES|QL views have the following limitations:
A role entry that grants read on a view name must not carry DLS or FLS restrictions. If it does, the query fails with a 403. DLS/FLS on the underlying indices is applied normally. Refer to view privileges.
Views may be nested up to depth 10.
View definitions can contain branched query plans created by FROM subqueries,
nested views, and FORK.
These branches can be nested, subject to the branch and command limits described
in How views execute.
Branching and nesting are allowed in combination only when there is no more than one branch point. A view can contain subqueries only when the resulting plan can be flattened through query compaction.
Views can reference remote indices in cross-cluster search, but you can't query a view that is defined on a remote cluster.
Remote views are invisible to source resolution. An exact reference such as FROM cluster:view returns an Unknown index error.
A query fails if its source expression matches a remote view.
Views are available in serverless and cross-project search, but with some limitations:
- You can define views in both origin and linked projects.
- CPS resolves index expressions against indices in every project that the
expression targets, but it resolves views only in the origin project. Views
in linked projects are invisible to the query, including through qualified
expressions such as
FROM linked-project:view. - CPS uses the same resolution process for index expressions in the top-level query and inside view definitions.
- An unqualified index expression can match a view in the origin project and indices in linked projects. CPS returns results from both.
A Query DSL filter on the request applies to the output of a view, after the commands in the view definition. If Elasticsearch can't translate a filter clause to ES|QL, it omits that clause for the view and returns a warning. Check the response warnings because omitting a clause can produce more results than the complete filter would.
For example, create a view that counts sales by region:
PUT /_query/view/sales_by_region
{
"query": "FROM sales | STATS total = COUNT(*) BY region"
}
The following request requires both should clauses to match:
POST /_query
{
"query": "FROM sales_by_region | KEEP region, total | LIMIT 1000",
"filter": {
"bool": {
"should": [
{ "term": { "region": "eu" } },
{ "term": { "total": 3 } }
],
"minimum_should_match": 2
}
}
}
ES|QL can't translate a minimum_should_match value greater than 1. It skips
the should group for the view, which can return more rows than the complete
filter would, and returns this warning:
The request filter could not be fully applied to view(s); the following Query DSL constructs are not supported and were skipped: [bool[minimum_should_match=2]] on view [sales_by_region]. Use a WHERE clause to filter rows from views instead
A Query DSL filter on the request applies to the source indices in the view definition, not to the view output.
A view definition can read from index aliases and data streams. An alias cannot point to a view.
An Elasticsearch transform is not an ES|QL source. To query transform results from a view, use the transform's destination index as the source in the view definition.
A view definition can read from a dataset, which ES|QL Data Federation uses to query external data.
wildcards_match_datasets
to true.
Only the following settings are supported in a view definition:
| Setting | Supported values |
|---|---|
unmapped_fields |
DEFAULT, NULLIFY, and LOAD |
wildcards_match_views |
true and false |
wildcards_match_datasets
|
true and false |
LOAD_ALL
is not supported in a view definition.
All other settings, including time_zone, are rejected in a view definition.
A view definition can't contain a SET directive.
The three settings supported in view definitions are scoped at every view boundary. Values from the calling query, request body, or an enclosing view don't enter the view. The cluster setting is used to set the default throughout the query, both inside and outside views. A value in a view definition overrides that default for its body, but doesn't enter a nested view. If neither is configured, the built-in default applies.
Other query settings are rejected in a view definition, but aren't scoped to
individual views. Their effective value for the calling query can apply while
evaluating a view. For example, time_zone can apply inside a view.
At a view boundary:
unmapped_fields: Fields loaded by an outer query arenullfor rows from a view unless the view produces those fields itself. If an outer setting would have loaded fields that the view doesn't produce, the response warning identifies the view and the fields that arenull.- For each supported wildcard setting, exact names work regardless of the
setting. A wildcard matches the corresponding target type only when the view
definition sets the setting to
true. If atruevalue from outside the view stops at the boundary and the wildcard would otherwise have matched targets, the response returns a warning that identifies the view and the missed matches.
The effective query settings apply throughout the query, including inside view bodies.
Query parameters are not allowed in a stored view definition. A calling query
can still use parameters in commands after FROM view_name, but those parameters
are not substituted into the view body.
A view that starts with TS
must also contain a STATS command after it. A view without STATS is
rejected when you create or update it. This ensures the view returns plain
tabular results. A PROMQL view must also use an aggregation function.
Query a time series view with FROM view_name. A view name cannot be used as a
source in TS or PROMQL. An exact view name in one of those source commands is
rejected, and a wildcard source expression skips matching views.
When you create or update a view, Elasticsearch validates its name, size, query syntax, and the settings that are allowed in a view definition.
TS is followed by STATS.
Elasticsearch doesn't resolve or execute the query, and it doesn't check whether referenced indices, fields, datasets, or views exist, or whether nested views form a cycle. Creation doesn't return warnings for those problems. Elasticsearch reports them when you query the view.
Deleting a resource doesn't check whether a view depends on it. Deleting an index, data stream, alias, dataset, or nested view can therefore cause existing views to fail when queried.
For a detailed comparison of views, subqueries, and FORK, refer to Combine and reuse ES|QL queries.
- ES|QL subqueries: nest queries inside other queries, in
FROMor withIN/NOT IN. FROMcommand: full reference for index expressions, where view names are used.- Query multiple indices: how index patterns, wildcards, and date math combine sources in a single
FROM.