﻿---
title: Define reusable queries with ES|QL views
description: 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...
url: https://www.elastic.co/elastic/docs-builder/docs/3856/reference/query-languages/esql/esql-views
products:
  - Elasticsearch
applies_to:
  - Elastic Cloud Serverless: Preview
  - Elastic Stack: Preview since 9.4
---

# 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`](https://www.elastic.co/elastic/docs-builder/docs/3856/reference/query-languages/esql/commands/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.


## Basic example

Here's how a view works in practice:
<stepper>
  <step title="Start with a query">
    ```esql
    FROM addresses
    | RENAME city.country.name AS country
    | EVAL country = CASE(country == "United States of America", "United States", country)
    | STATS count=COUNT() BY country
    ```


    | count:long | country:keyword |
    |------------|-----------------|
    | 1          | Japan           |
    | 1          | Netherlands     |
    | 1          | United States   |
  </step>

  <step title="Save it as a view">
    ```json

    {
        "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
            """
    }
    ```
  </step>

  <step title="Reference it by name, just like an index">
    ```esql
    FROM country_addresses
    ```


    | count:long | country:keyword |
    |------------|-----------------|
    | 1          | Japan           |
    | 1          | Netherlands     |
    | 1          | United States   |
  </step>
</stepper>


## When to use views

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 `FROM` clause.
- **Simplify queries for downstream tools.** Dashboards, alerts, or ad-hoc analysts can query `FROM my_view` without needing to know the indices or processing commands behind it.


## Create and manage views

Use the REST API to create, update, delete, and list views:
- [Create or update a view](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-put-view)
- [Delete a view](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-delete-view)
- [Get or list views](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-esql-get-view)


## Query a view

Use views as if they were ordinary indices:
```esql
FROM index_pattern
```

Where `index_pattern` is a comma-separated list of index or view names, including
wildcards and date-math.

## Examples

The following examples show how to use views within the `FROM` command.

### Combine data from multiple indices

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 our `airports` index
- `country_addresses` - reports counts of documents per country from our `addresses` index
- `country_languages` - reports counts of documents per country from our `languages` index

Now we can query these together with a query like:
```esql
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`.

### Use wildcards

```esql
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.

### Use LOOKUP JOIN inside a view

We can define views with complex queries, including commands like `LOOKUP JOIN`:
```json

{
    "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:
```esql
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                 |


### Views with METADATA

The [`METADATA` directive](https://www.elastic.co/elastic/docs-builder/docs/3856/reference/query-languages/esql/esql-metadata-fields) is supported both inside and outside a view, and
follows the same rules as observed for [`METADATA` in subqueries](/elastic/docs-builder/docs/3856/reference/query-languages/esql/esql-from-subquery#subqueries-with-metadata).
Inside the view it generates columns, just like other fields, and these can be used for filtering and as output columns.
Outside the view it generates `null` values.
Note that this is a known limitation of the current tech-preview, and is anticipated to be addressed in a future update,
at which point `METADATA _index` will contain the name of the view.

## How views execute

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.

### Execution model

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](https://www.elastic.co/elastic/docs-builder/docs/3856/reference/query-languages/esql/esql-from-subquery) and [`FORK`](https://www.elastic.co/elastic/docs-builder/docs/3856/reference/query-languages/esql/commands/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`.

### Nesting and branching

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`](https://www.elastic.co/elastic/docs-builder/docs/3856/reference/query-languages/esql/commands/fork). 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.
Branching and nesting are allowed in combination as long as there is never more than one branch point. This means nested branching has restrictions:
- A view can contain subqueries, but that view cannot be used together with other views, and the subqueries can only reference nested views that contain no further branching.
- A subquery can contain views, but those views must not introduce any additional branch points via subqueries or `FORK`.


### Query compaction

When a view definition itself contains branches (subqueries or references to other views), those inner branches would normally create a second level of branching, which ES|QL does not allow. Query compaction solves this by flattening the inner branches into the outer branch set, producing a single-level plan.
The following example shows how compaction works. Two views are each defined as a pair of subqueries:
```json

{
    "query": """
        FROM (
            FROM app-events-* | KEEP msg, level
        ), (
            FROM auth-events-* | KEEP msg, level
        )
       """
}
```

```json

{
    "query": """
        FROM (
            FROM nginx-events-* | KEEP msg, level
        ), (
            FROM apache-events-* | KEEP msg, level
        )
       """
}
```

A query references both views alongside a regular index:
```esql
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:
```mermaid
flowchart TD
    S["STATS count(msg) BY level"]
    S --> O["other-events"]
    S --> VX["view_x"]
    S --> VY["view_y"]
    VX --> AX["app-events-*"]
    VX --> AU["auth-events-*"]
    VY --> NG["nginx-events-*"]
    VY --> AP["apache-events-*"]
```

Compaction flattens the inner view branches into the outer branch set, producing a single-level plan with five branches:
```mermaid
flowchart TD
    S["STATS count(msg) BY level"]
    S --> O["other-events"]
    S --> AX["app-events-*"]
    S --> AU["auth-events-*"]
    S --> NG["nginx-events-*"]
    S --> AP["apache-events-*"]
```

Compaction does **not** apply if the view definition contains any processing commands after its subqueries. Those commands need to run on the combined branch output, so the branch level cannot be collapsed and the query will fail.

## Limitations

ES|QL views have the following limitations:

#### Branching inside views

Commands that also generate branched query plans
(`FORK`, [subqueries](https://www.elastic.co/elastic/docs-builder/docs/3856/reference/query-languages/esql/esql-subquery))
are usually not allowed within a view definition, unless the branch points
can be merged via
[query compaction](#query-compaction).
Views may be nested up to depth 10. Branching and nesting are allowed in
combination as long as there is never more than one branch point:
- A view can contain subqueries, but that view cannot be used together with
  other views, and the subqueries can only reference nested views that contain
  no further branching.
- A subquery can contain views, but those views must not introduce any
  additional branch points via subqueries or `FORK`.


#### Cross-cluster and serverless

Views are supported in [Cross-cluster search](https://www.elastic.co/elastic/docs-builder/docs/3856/reference/query-languages/esql/esql-cross-clusters) with some limitations:
- Remote views in CCS are not allowed (ie. `FROM cluster:view` will only
  match remote indexes with the name `view`. If a remote view is found,
  the query will fail).
- If a remote index matches a local view name, the query will fail.

Views are available in serverless and [Cross-project search](https://www.elastic.co/elastic/docs-builder/docs/3856/reference/query-languages/esql/esql-cross-serverless-projects), but with some limitations:
- You can define views in both origin and linked projects.
- CPS resolves index expressions against both indices and views in every project
  that the expression targets. CPS uses the same 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. However, the query
  fails if the expression also matches a view in a linked project, because CPS
  cannot query views in linked projects.


#### Query parameters

Query parameters are not allowed in the view definition, and therefore query
parameters in the main query will never impact the view results.

#### Known issues (tech preview)

Views are in tech-preview and there are a number of known issues, or behavior
that is likely to change in the future:
- Query DSL filtering on the main query will currently affect the source
  indices in the view definition, and this will change in later releases.
  - The future design will have the query filtering impact the output of the
  view, not the source indices.
- `METADATA` directives inside and outside a view definition behave the same
  as they do for
  [`METADATA` in subqueries](/elastic/docs-builder/docs/3856/reference/query-languages/esql/esql-from-subquery#subqueries-with-metadata).
  This will change for views.


## Compare views, subqueries, and FORK

For a detailed comparison of views, subqueries, and `FORK`, refer to [Combine and reuse ES|QL queries](/elastic/docs-builder/docs/3856/reference/query-languages/esql/esql-combine-reuse-queries#comparing-views-subqueries-and-fork).

## Related pages

- [ES|QL subqueries](https://www.elastic.co/elastic/docs-builder/docs/3856/reference/query-languages/esql/esql-subquery): nest queries inside other queries, either in `FROM` or `WHERE`.
- [`FROM` command](https://www.elastic.co/elastic/docs-builder/docs/3856/reference/query-languages/esql/commands/from): full reference for index expressions, where view names are used.
- [Query multiple indices](https://www.elastic.co/elastic/docs-builder/docs/3856/reference/query-languages/esql/esql-multi-index): how index patterns, wildcards, and date math combine sources in a single `FROM`.