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.
Steps to run an ES|QL query in Elasticsearch:
- Create a disposable index with fields that are easy to query.
$ 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.
- Add a few sample documents.
$ 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.
- Run an ES|QL query and request terminal-friendly text output.
$ 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 timeoutFROM names the index, alias, or data stream. WHERE filters rows, SORT controls order, KEEP selects columns, and LIMIT keeps the returned table bounded.
- Request structured JSON when another tool needs to parse the result.
$ 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.
- Remove the disposable sample index.
$ 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.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.