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:

  1. 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.

  2. 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.

  3. 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 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.

  4. 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.

  5. 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.