Running an ES|QL query in Elasticsearch turns index, alias, or data stream data into a table without building a full Query DSL request. It is useful for checking logs, metrics, or application events when the answer is easier to read as columns and rows.
The ES|QL REST API uses POST /_query and reads the query string from the JSON request body. A small disposable index keeps returned rows predictable during validation, and the same request shape works against an existing index, alias, or data stream.
Use format=txt when a terminal table is enough and format=json when another script or tool needs the returned column metadata and values. ES|QL returns up to 1,000 rows by default, uses LIMIT for smaller or larger result sets, and caps ordinary result output at 10,000 rows unless an administrator changes the ES|QL result-truncation settings.
$ curl --silent --show-error --fail --header "Content-Type: application/json" --request PUT "http://localhost:9200/app-events-esql-2026.01?pretty" --data '{
"settings": {
"number_of_replicas": 0
},
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"service": { "type": "keyword" },
"level": { "type": "keyword" },
"message": { "type": "text" },
"duration_ms": { "type": "integer" }
}
}
}'
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "app-events-esql-2026.01"
}
The sample keeps replicas at 0 so a one-node validation cluster can allocate the index. Use the real secured endpoint and authentication header for production clusters.
$ curl --silent --show-error --fail --header "Content-Type: application/x-ndjson" --request POST "http://localhost:9200/app-events-esql-2026.01/_bulk?refresh=wait_for&pretty&filter_path=errors,items.*.status,items.*.result" --data-binary @- <<'NDJSON'
{ "index": { "_id": "evt-1001" } }
{ "@timestamp": "2026-04-02T06:15:00Z", "service": "checkout", "level": "ERROR", "message": "payment authorization timeout", "duration_ms": 1840 }
{ "index": { "_id": "evt-1002" } }
{ "@timestamp": "2026-04-02T06:16:30Z", "service": "checkout", "level": "INFO", "message": "cart updated", "duration_ms": 42 }
{ "index": { "_id": "evt-1003" } }
{ "@timestamp": "2026-04-02T06:17:45Z", "service": "payments", "level": "ERROR", "message": "card gateway timeout", "duration_ms": 2310 }
NDJSON
{
"errors" : false,
"items" : [
{
"index" : {
"result" : "created",
"status" : 201
}
},
{
"index" : {
"result" : "created",
"status" : 201
}
},
{
"index" : {
"result" : "created",
"status" : 201
}
}
]
}
Each NDJSON action line is followed by the source document for that action. When data already exists, replace app-events-esql-2026.01 with the real target and run the ES|QL request directly.
$ curl --silent --show-error --fail --header "Content-Type: application/json" --request POST "http://localhost:9200/_query?format=txt" --data '{
"query": "FROM app-events-esql-2026.01 | WHERE level == \"ERROR\" | SORT @timestamp DESC | KEEP service, level, message | LIMIT 5"
}'
service | level | message
---------------+---------------+-----------------------------
payments |ERROR |card gateway timeout
checkout |ERROR |payment authorization timeout
FROM names the index, alias, or data stream. WHERE filters rows, SORT controls order, KEEP selects columns, and LIMIT keeps the returned table bounded.
$ curl --silent --show-error --fail --header "Content-Type: application/json" --request POST "http://localhost:9200/_query?format=json&pretty&filter_path=is_partial,documents_found,columns,values" --data '{
"query": "FROM app-events-esql-2026.01 | WHERE level == \"ERROR\" | STATS errors = COUNT(*) BY service | SORT errors DESC"
}'
{
"is_partial" : false,
"documents_found" : 2,
"columns" : [
{
"name" : "errors",
"type" : "long"
},
{
"name" : "service",
"type" : "keyword"
}
],
"values" : [
[
1,
"payments"
],
[
1,
"checkout"
]
]
}
JSON responses return columns metadata and row-oriented values by default. is_partial should be false when every shard needed for the query answered successfully.
$ curl --silent --show-error --fail --request DELETE "http://localhost:9200/app-events-esql-2026.01?pretty"
{
"acknowledged" : true
}
Delete only the sample index created for testing. Do not run this cleanup command against an existing production index, alias, or data stream.