Terms aggregation

Terms aggregation #

A multi-bucket value source based aggregation where buckets are dynamically built - one per unique value. It answers “group by this field, count (and aggregate) per value” without knowing the values in advance, which a fixed-bucket aggregation like histogram or range cannot do.

How it works #

Every matching document is placed in the bucket keyed by its value of field. Multi-valued fields bucket per element: a document whose tags holds ["search", "architecture"] is counted once in the search bucket and once in the architecture bucket. Documents without the field are skipped — unless missing is set, in which case they land together in one bucket keyed by that value.

Buckets are ordered by doc_count descending, with ties broken by key ascending. The tie-break is numeric-aware: when every key parses as a number, keys compare as numbers (so 2 sorts before 10); otherwise they compare as strings. Numeric keys are normalized — an integral double and a long with the same value share one bucket key.

min_doc_count filtering happens before the size truncation, so size counts surviving buckets only. With min_doc_count: 0, terms the query never matched still materialize as count-0 buckets (drawn from the column dictionary) — useful with an explicit include list to guarantee a complete report.

include/exclude restrict candidate keys before counting. Both accept an exact-value array (numeric-aware: "10", 10 and 10.0 are the same key) or a single wildcard string (prefix*, *suffix). include also accepts { "partition": p, "num_partitions": n }: keys are routed to a partition by a Murmur3 hash (numeric keys hashed as longs), so several requests can split a high-cardinality field into disjoint slices — request p = 0..n-1 to cover every key exactly once.

Examples #

One bucket per type — GROUP BY on the SQL side (the pill beside the Run button), ordered by the count:

POST /sales/_search
{
  "aggs": {
    "by_type": {
      "terms": {
        "field": "type"
      }
    }
  }
}

Response:

{
  ...
  "aggregations": {
    "by_type": {
      "buckets": [
        { "key": "t-shirt", "doc_count": 60 },
        { "key": "hat", "doc_count": 30 },
        { "key": "mug", "doc_count": 20 },
        { "key": "sticker", "doc_count": 10 }
      ]
    }
  }
}

Use cases #

  • Category leaderboards. sales.terms(type) returns the t-shirt: 60 / hat: 30 / mug: 20 / sticker: 10 ranking shown above; nest a sum sub-aggregation to rank by revenue instead of count.
  • Multi-value tag clouds. articles carry a tags array; a terms aggregation on it spreads each article across every tag it carries, giving per-tag article counts.
  • Log level mix. logs.terms(level) yields info: 100 / error: 25 / warn: 25 — a one-request health summary.
  • Segment rollups. customers.terms(tier) splits the 20 customers into silver: 8 / bronze: 8 / gold: 4 for per-tier metrics.
  • Sliced exhaustive scans. Combine include partitioning with size to walk a high-cardinality field slice by slice when one request cannot hold all buckets.

Parameters for terms #

  • field
    (Required, string) Field you wish to aggregate.
  • size
    (Optional, integer) Maximum number of buckets to return. Defaults to 10.
  • min_doc_count
    (Optional, integer) Drop buckets with fewer matching documents. Defaults to 1; 0 keeps empty buckets — only meaningful together with an explicit include list.
  • missing
    (Optional, value) Value substituted for documents without the field (any scalar; those documents land in one bucket keyed by it).
  • include
    (Optional) Restricts bucket keys: an exact-value array, a wildcard string (prefix* / *suffix), or { "partition": <p>, "num_partitions": <n> }.
  • exclude
    (Optional) Excludes bucket keys: an exact-value array or a wildcard string (negated include).
  • aggs
    (Optional, object) Nested sub-aggregations, computed per bucket.

Bucket order is fixed to doc_count descending (key-ascending tie-break); the order parameter is not accepted.

Calendar October 6, 2026
Edit Edit this page