Skip to main content
Aggregations are a framework for performing complex data analysis and summarization over indexed documents. They let you compute statistics, identify trends, and extract patterns from large datasets — all within a single search request. Elasticsearch organizes aggregations into three families:

Metric

Compute numeric values from field data — averages, sums, percentiles, and more.

Bucket

Group documents into named buckets based on field values, ranges, or time intervals.

Pipeline

Take input from other aggregations rather than documents to compute derived statistics.

The size: 0 trick

When you only need aggregation results and not the matching documents, set "size": 0. This skips collecting, sorting, and serializing hits — which significantly reduces response size and memory usage:
POST /orders/_search
{
  "size": 0,
  "aggs": {
    "total_revenue": {
      "sum": { "field": "amount" }
    }
  }
}
Always use size: 0 for aggregation-only queries. It can reduce response time by an order of magnitude on large indices.

Bucket aggregations

Bucket aggregations create groups of documents. Each bucket is associated with a key and a document count. Bucket aggregations can contain nested sub-aggregations.
Groups documents by the unique values of a field. Returns the top N buckets by document count by default.
POST /orders/_search
{
  "size": 0,
  "aggs": {
    "by_status": {
      "terms": {
        "field": "status",
        "size": 10
      }
    }
  }
}
The size parameter controls how many unique values to return (default 10).
Groups documents into time buckets of a fixed interval. Essential for time-series analysis.
POST /logs/_search
{
  "size": 0,
  "aggs": {
    "events_per_hour": {
      "date_histogram": {
        "field": "@timestamp",
        "calendar_interval": "1h",
        "format": "yyyy-MM-dd HH:mm"
      }
    }
  }
}
Groups numeric field values into fixed-width intervals.
POST /products/_search
{
  "size": 0,
  "aggs": {
    "price_distribution": {
      "histogram": {
        "field": "price",
        "interval": 50,
        "min_doc_count": 1
      }
    }
  }
}
Groups documents into manually defined numeric or date ranges. Each range is defined with from and to bounds.
POST /products/_search
{
  "size": 0,
  "aggs": {
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 50 },
          { "from": 50, "to": 200 },
          { "from": 200 }
        ]
      }
    }
  }
}
Creates a single bucket containing only documents that match a filter. Useful for computing metrics on a subset without changing the outer query scope.
POST /sales/_search
{
  "size": 0,
  "aggs": {
    "premium_sales": {
      "filter": { "term": { "tier": "premium" } },
      "aggs": {
        "avg_order_value": {
          "avg": { "field": "amount" }
        }
      }
    }
  }
}
Generates composite buckets from multiple value sources and supports efficient pagination over aggregation results. Use this instead of terms when you need to paginate through all unique combinations.
POST /events/_search
{
  "size": 0,
  "aggs": {
    "by_region_and_status": {
      "composite": {
        "size": 100,
        "sources": [
          { "region": { "terms": { "field": "region" } } },
          { "status": { "terms": { "field": "status" } } }
        ]
      }
    }
  }
}
Use the after parameter with the after_key from the response to paginate.

Metric aggregations

Metric aggregations compute numeric values from document field data.
These aggregations produce a single numeric output:
AggregationDescription
avgAverage of field values
sumSum of field values
minMinimum field value
maxMaximum field value
cardinalityApproximate count of distinct values (HyperLogLog++)
value_countCount of values (including duplicates)
POST /transactions/_search
{
  "size": 0,
  "aggs": {
    "total":   { "sum":         { "field": "amount" } },
    "average": { "avg":         { "field": "amount" } },
    "lowest":  { "min":         { "field": "amount" } },
    "highest": { "max":         { "field": "amount" } },
    "unique_customers": { "cardinality": { "field": "customer_id" } }
  }
}

Pipeline aggregations

Pipeline aggregations operate on the output of other aggregations rather than on raw documents. They enable derived metrics, smoothing, and cross-bucket computations.
Computes a rolling average over ordered buckets from a sibling histogram or date_histogram aggregation.
"sales_moving_avg": {
  "moving_avg": {
    "buckets_path": "daily_sales>total",
    "window": 7
  }
}
Calculates the first-order derivative (rate of change) between adjacent buckets.
"error_rate_change": {
  "derivative": {
    "buckets_path": "hourly_errors>_count"
  }
}
Computes the running total of a sibling metric aggregation across ordered buckets.
"running_total": {
  "cumulative_sum": {
    "buckets_path": "daily_revenue>total"
  }
}
Executes a script against per-bucket metric values to compute a derived value. All referenced metrics must be siblings.
"conversion_rate": {
  "bucket_script": {
    "buckets_path": {
      "orders": "order_count",
      "visits": "visit_count"
    },
    "script": "params.orders / params.visits * 100"
  }
}

Nesting aggregations

Bucket aggregations can contain nested sub-aggregations. Sub-aggregations are executed within the context of each bucket, enabling multi-level analysis.

Example: nested terms + date_histogram + avg

This query groups sales by region, then further breaks each region’s sales down by month, and computes the average order value per month per region:
POST /sales/_search
{
  "size": 0,
  "aggs": {
    "by_region": {
      "terms": {
        "field": "region",
        "size": 10
      },
      "aggs": {
        "sales_over_time": {
          "date_histogram": {
            "field": "order_date",
            "calendar_interval": "month"
          },
          "aggs": {
            "avg_order_value": {
              "avg": {
                "field": "amount"
              }
            }
          }
        }
      }
    }
  }
}
The response structure mirrors the nesting:
{
  "aggregations": {
    "by_region": {
      "buckets": [
        {
          "key": "us-east",
          "doc_count": 4821,
          "sales_over_time": {
            "buckets": [
              {
                "key_as_string": "2024-10-01",
                "doc_count": 1203,
                "avg_order_value": { "value": 84.50 }
              },
              {
                "key_as_string": "2024-11-01",
                "doc_count": 1587,
                "avg_order_value": { "value": 91.20 }
              }
            ]
          }
        }
      ]
    }
  }
}

Aggregation scope

By default, aggregations run over all documents that match the query. To run an aggregation on a specific subset without affecting the main query, use a filter aggregation or the post_filter parameter.
POST /products/_search
{
  "query": {
    "term": { "category": "electronics" }
  },
  "size": 10,
  "aggs": {
    "in_stock_avg_price": {
      "filter": { "term": { "in_stock": true } },
      "aggs": {
        "avg_price": { "avg": { "field": "price" } }
      }
    }
  }
}
The search.max_buckets cluster setting limits the total number of buckets that can be returned in a single response. The default is 65,536. Queries that generate more buckets are rejected with an error.

Build docs developers (and LLMs) love