Build the mental model
Aggregations are Elasticsearch's analytics framework, shifting the question from "which documents match" (search) to "what patterns or summaries exist in the data" (analytics) — serving the same purpose as SQL's `GROUP BY` combined with `AVG()`/`COUNT()`. A metric aggregation (`avg`, `sum`, `min`, `max`, `stats`) takes a numeric field as input and returns one computed value (say, the average lesson count across all tutorials). A bucket aggregation (`terms`, `date_histogram`, `range`) groups documents into buckets by criteria — a `terms` aggregation builds one bucket per unique value of a `keyword` field and returns the document count in each, which is exactly why `terms` should run on a `keyword` or numeric field (running it on a `text` field creates one bucket per token/individual word, not the result you intended). Aggregations can be attached alongside a search query — the `query` first narrows the document set, and the aggregation then runs only over that narrowed set, so "filtered analytics" is achievable in a single request. Think of it like pulling both a pie chart (buckets = category counts) and an average score (a metric) out of survey data at the same time.
Connect it to a real scenario
On the Tutorial Platform's admin analytics dashboard, a chart answering "how many tutorials exist per topic" comes from a single `terms` bucket aggregation on the `tags` field — PostgreSQL would need a separate `GROUP BY tag` query, while Elasticsearch just attaches an `aggs` object next to the search query. Filter to "published in the last 30 days" and then run an `avg` aggregation over that narrowed set to also pull "average lesson count among recent tutorials" — search and analytics come back from a single request.
Try the working example
GET /tutorials/_search
{
"size": 0,
"query": {
"range": { "publishedAt": { "gte": "now-30d" } }
},
"aggs": {
"tutorials_per_tag": {
"terms": { "field": "tags" }
},
"avg_lesson_count": {
"avg": { "field": "lessonCount" }
}
}
}You get per-tag document count buckets and the average lesson count, for tutorials published in the last 30 days.5-minute try-it
Write a query combining a `terms` bucket aggregation on `difficulty` with a `max` metric aggregation on `lessonCount` in one request (use `size: 0`).
One important caution
Running a `terms` bucket aggregation on a `text` field (a field tokenized by an analyzer) — it creates a bucket per individual word, completely diverging from the intended category counts.
Wanting only aggregation results but leaving `size` at its default (10) — ten documents come back in the hits array anyway, needlessly bloating the response size and bandwidth; set `size: 0` instead.
Elasticsearch Guide — Aggregations — Elastic