Skip to main content
Elasticsearch Query Language (ES|QL) is a piped query language for filtering, transforming, and analyzing data. You can author ES|QL queries to find specific events, perform statistical analysis, and create visualizations. ES|QL uses pipes (|) to manipulate and transform data step by step. The output of one operation becomes the input for the next, enabling complex data transformations and analysis in a single readable query.
ES|QL is optimized for analytics on large datasets. Use it for aggregations, time-series analysis, and computed fields. For precise document retrieval with complex scoring, Query DSL remains the better fit.

How it works

A query is a pipeline of commands separated by the pipe character (|). Each command operates on the output of the previous one:
FROM sample_data
| WHERE @timestamp > NOW() - 24h
| STATS avg_duration = AVG(duration) BY service
| SORT avg_duration DESC
| LIMIT 10
This query:
  1. Reads from sample_data
  2. Filters to documents from the last 24 hours
  3. Computes the average duration grouped by service
  4. Sorts by average duration descending
  5. Returns the top 10 results

Running ES|QL queries

Send queries to the _query endpoint with a Content-Type of application/json:
POST /_query
Content-Type: application/json

{
  "query": "FROM logs-* | WHERE level == \"error\" | LIMIT 100"
}
The response returns rows and column metadata:
{
  "columns": [
    { "name": "@timestamp", "type": "date" },
    { "name": "message",    "type": "keyword" },
    { "name": "level",      "type": "keyword" }
  ],
  "values": [
    ["2024-11-15T10:30:00Z", "Connection refused", "error"],
    ["2024-11-15T10:31:00Z", "Timeout exceeded",   "error"]
  ]
}

Source commands

Source commands begin every ES|QL pipeline. They define the data to operate on.

FROM

Reads documents from one or more indices or data streams. Supports wildcards and comma-separated patterns.
FROM logs-*, metrics-*
| LIMIT 100

Processing commands

Processing commands transform the data flowing through the pipeline.
Keeps only rows matching the condition. Supports comparison operators, boolean logic, and functions.
FROM logs
| WHERE level == "error" AND service != "heartbeat"
| LIMIT 50
Computes aggregations over rows, optionally grouped with BY. Produces one row per group.
FROM orders
| STATS
    total_revenue = SUM(amount),
    order_count   = COUNT(*),
    avg_value     = AVG(amount)
  BY region
| SORT total_revenue DESC
Creates new columns by evaluating expressions against each row. The original row is preserved with the new field appended.
FROM sales
| EVAL
    revenue_usd = quantity * unit_price,
    is_large    = quantity > 100
| WHERE is_large
Sorts rows by one or more expressions. ASC is the default; use DESC for descending order. NULLS FIRST and NULLS LAST control null ordering.
FROM products
| SORT price ASC, name ASC NULLS LAST
Restricts the number of rows returned by the pipeline. Always include LIMIT in exploratory queries to avoid retrieving millions of rows.
FROM events
| SORT @timestamp DESC
| LIMIT 20
KEEP retains only the specified columns. DROP removes the specified columns. Both accept wildcards.
FROM logs
| KEEP @timestamp, message, level, service

FROM logs
| DROP internal_*, debug_*
Renames one or more columns using the AS keyword.
FROM metrics
| RENAME cpu_percent AS cpu_usage, mem_bytes AS memory
Both commands extract structured fields from unstructured string fields.
  • DISSECT uses a fixed-separator pattern.
  • GROK uses named regular expression patterns (compatible with Logstash grok).
FROM raw_logs
| DISSECT message "%{ts} %{level} %{msg}"

FROM apache_logs
| GROK message "%{IPORHOST:client_ip} %{HTTPDATE:timestamp} \"%{WORD:method} %{URIPATHPARAM:path}\""
Converts rows with multi-value fields into multiple rows, one per value. Useful when a field contains an array of values.
FROM products
| MV_EXPAND tags
| STATS count = COUNT(*) BY tags
| SORT count DESC
Looks up fields from an enrich policy and appends them to each row. Similar to a left join against a reference dataset.
FROM orders
| ENRICH customer_policy ON customer_id
  WITH name, tier, region

Functions

ES|QL includes a broad set of built-in functions.
FunctionDescription
NOW()Current timestamp
DATE_TRUNC(interval, date)Truncates a date to the specified interval
DATE_FORMAT(format, date)Formats a date as a string
DATE_EXTRACT(part, date)Extracts a date component (year, month, etc.)
FROM events
| EVAL hour = DATE_EXTRACT("hour_of_day", @timestamp)
| STATS count = COUNT(*) BY hour
| SORT hour ASC

Examples

1

Basic retrieval

Return the 5 most recent error log entries:
FROM logs-*
| WHERE level == "error"
| SORT @timestamp DESC
| LIMIT 5
2

Aggregation with grouping

Count events by status code over the last hour:
FROM access-logs
| WHERE @timestamp > NOW() - 1h
| STATS request_count = COUNT(*) BY status_code
| SORT request_count DESC
3

Computed fields

Calculate latency buckets and find the 99th percentile per service:
FROM apm-*
| EVAL latency_ms = duration / 1000
| STATS p99 = PERCENTILE(latency_ms, 99) BY service.name
| SORT p99 DESC
| LIMIT 20
4

String parsing

Extract HTTP method and path from a raw access log field:
FROM access-logs
| GROK message "%{WORD:method} %{URIPATHPARAM:path} HTTP/%{NUMBER:http_version}"
| STATS count = COUNT(*) BY method, path
| SORT count DESC
| LIMIT 10

Circuit breaker settings

ES|QL enforces memory limits using circuit breakers to protect cluster stability. You can tune these limits via the circuit breaker settings in your cluster configuration. Queries that exceed the memory limit are terminated with an error rather than causing a node to run out of heap.

Build docs developers (and LLMs) love