# OpenSearch Aggregations Explained: Terms, Stats, Range, and Nested Stats in AWS Samples

> Explore OpenSearch aggregations like terms, stats, range, and nested_stats with these AWS samples. Learn Python backend and React frontend implementations for powerful data analysis.

- Repository: [AWS Samples/sample-for-amazon-opensearch-service-tutorials-101](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101)
- Tags: tutorial
- Published: 2026-02-25

---

**The aws-samples/sample-for-amazon-opensearch-service-tutorials-101 repository demonstrates four core OpenSearch aggregation types—`terms`, `stats`, `range`, and `nested_stats`—implemented in Python backend logic and invoked through a React TypeScript frontend.**

OpenSearch aggregations enable powerful analytics and data summarization directly within your search queries. This tutorial repository provides a complete working implementation showing how to bucket documents by keyword fields, calculate statistical metrics, group by numeric ranges, and perform nested metric calculations within parent buckets.

## Types of OpenSearch Aggregations Demonstrated

The repository implements four distinct aggregation patterns in [`artifacts/search_lambda/opensearch_search.py`](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101/blob/main/artifacts/search_lambda/opensearch_search.py), each serving different analytical purposes for the product catalog dataset.

### Terms Aggregations

**Terms aggregations** group documents into buckets based on unique values of a keyword field, such as product categories or colors.

In [`opensearch_search.py`](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101/blob/main/opensearch_search.py) (lines 141-147), the backend constructs a `terms` aggregation on fields like `category.keyword`:

```python

# From opensearch_search.py lines 141-147

if agg['type'] == 'terms':
    agg_body = {
        "terms": {
            "field": agg['field'],
            "size": agg.get('size', 10)
        }
    }

```

The React frontend requests this aggregation in [`keyword-aggregations-page.tsx`](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101/blob/main/keyword-aggregations-page.tsx) (lines 99-104) by sending a payload with `type: "terms"`, enabling the UI to render category distribution charts and filterable bucket lists.

### Stats Aggregations

**Stats aggregations** compute comprehensive statistical metrics—count, minimum, maximum, average, and sum—for numeric fields like pricing or inventory quantities.

The backend implementation in [`opensearch_search.py`](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101/blob/main/opensearch_search.py) (lines 148-154) builds a `stats` aggregation:

```python

# From opensearch_search.py lines 148-154

elif agg['type'] == 'stats':
    agg_body = {
        "stats": {
            "field": agg['field']
        }
    }

```

When the UI sends `type: "stats"` in [`keyword-aggregations-page.tsx`](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101/blob/main/keyword-aggregations-page.tsx) (lines 111-114), OpenSearch returns a statistical summary that the frontend displays as metric cards showing average price, price ranges, and total inventory value across the product catalog.

### Range Aggregations

**Range aggregations** divide documents into user-defined numeric buckets based on specified boundaries, ideal for creating price range filters or date histograms.

Implemented in [`opensearch_search.py`](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101/blob/main/opensearch_search.py) (lines 155-162), this aggregation accepts an array of ranges:

```python

# From opensearch_search.py lines 155-162

elif agg['type'] == 'range':
    agg_body = {
        "range": {
            "field": agg['field'],
            "ranges": agg['ranges']  # User-defined boundaries

        }
    }

```

The frontend configuration in [`keyword-aggregations-page.tsx`](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101/blob/main/keyword-aggregations-page.tsx) (lines 116-124) allows users to define specific price brackets (e.g., $0-$50, $50-$100, $100+), which OpenSearch uses to bucket products accordingly for faceted navigation interfaces.

### Nested Stats Aggregations

**Nested stats aggregations** combine bucket aggregations with metric calculations, first grouping documents by a parent field (like category) then computing metrics (like average price) within each bucket.

This advanced pattern appears in [`opensearch_search.py`](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101/blob/main/opensearch_search.py) (lines 163-176):

```python

# From opensearch_search.py lines 163-176

elif agg['type'] == 'nested_stats':
    agg_body = {
        "terms": {
            "field": agg['field'],  # Parent bucket field

            "size": agg.get('size', 10),
            "aggs": {
                "nested_metric": {
                    agg['metric_type']: {  # e.g., "avg", "sum"

                        "field": agg['metric_field']
                    }
                }
            }
        }
    }

```

When triggered by `type: "nested_stats"` from [`keyword-aggregations-page.tsx`](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101/blob/main/keyword-aggregations-page.tsx) (lines 126-134), this aggregation enables analytical displays showing average price per category, total inventory per manufacturer, or other multi-level metrics essential for business intelligence dashboards.

## How Aggregations Flow Through the Application

The repository implements a clear request-response pipeline connecting the React frontend to the OpenSearch domain via AWS Lambda.

1. **Client Request Assembly** – The React page [`keyword-aggregations-page.tsx`](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101/blob/main/keyword-aggregations-page.tsx) constructs a JSON payload containing an `aggregations` array with type definitions (`terms`, `stats`, `range`, `nested_stats`) and POSTs it to the `/search` endpoint.

2. **Lambda Handler Processing** – The `search_products` function in [`opensearch_search.py`](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101/blob/main/opensearch_search.py) receives the request, detects `search_type: "aggregations"`, and iterates over the aggregation definitions.

3. **Aggregation DSL Construction** – For each aggregation entry, the code matches the `agg_type` against the four supported patterns and appends the appropriate OpenSearch DSL fragment under the `aggs` node of the query body.

4. **OpenSearch Execution** – The constructed query executes against the Amazon OpenSearch Service domain, returning bucket counts, statistical values, and nested metric results.

5. **UI Rendering** – The frontend receives the aggregation results and renders them as charts, progress bars, statistical cards, and faceted filter controls.

## Implementing OpenSearch Aggregations in Python

The backend implementation in [`opensearch_search.py`](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101/blob/main/opensearch_search.py) demonstrates a clean pattern for dynamically building aggregation queries based on frontend configuration:

```python
def build_aggregation_query(aggregations):
    """
    Constructs OpenSearch aggregation DSL from frontend configuration.
    Based on implementation in opensearch_search.py lines 141-176.
    """
    aggs_body = {}
    
    for agg in aggregations:
        agg_name = agg['name']
        agg_type = agg['type']
        
        if agg_type == 'terms':
            aggs_body[agg_name] = {
                "terms": {
                    "field": agg['field'],
                    "size": agg.get('size', 10)
                }
            }
        elif agg_type == 'stats':
            aggs_body[agg_name] = {
                "stats": {
                    "field": agg['field']
                }
            }
        elif agg_type == 'range':
            aggs_body[agg_name] = {
                "range": {
                    "field": agg['field'],
                    "ranges": agg['ranges']
                }
            }
        elif agg_type == 'nested_stats':
            aggs_body[agg_name] = {
                "terms": {
                    "field": agg['field'],
                    "size": agg.get('size', 10),
                    "aggs": {
                        "nested_metric": {
                            agg['metric_type']: {
                                "field": agg['metric_field']
                            }
                        }
                    }
                }
            }
    
    return {"aggs": aggs_body}

```

This pattern allows the frontend to request complex analytical queries without hardcoding aggregation logic, enabling dynamic faceted search and business intelligence visualizations.

## Summary

The **aws-samples/sample-for-amazon-opensearch-service-tutorials-101** repository demonstrates four essential **OpenSearch aggregations** for building analytical search applications:

- **Terms aggregations** bucket documents by unique keyword values (e.g., categories, colors) using [`opensearch_search.py`](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101/blob/main/opensearch_search.py) lines 141-147
- **Stats aggregations** calculate comprehensive statistics (count, min, max, avg, sum) on numeric fields via lines 148-154
- **Range aggregations** create user-defined numeric buckets (e.g., price ranges) implemented in lines 155-162
- **Nested stats aggregations** combine parent buckets with sub-metrics (e.g., average price per category) using lines 163-176

These aggregations flow from the React frontend ([`keyword-aggregations-page.tsx`](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101/blob/main/keyword-aggregations-page.tsx)) through the Lambda backend to Amazon OpenSearch Service, enabling faceted navigation and statistical dashboards.

## Frequently Asked Questions

### What is the difference between terms and stats aggregations in OpenSearch?

**Terms aggregations** group documents into buckets based on unique values of a keyword field (like product categories or brands), while **stats aggregations** compute mathematical statistics (count, minimum, maximum, average, and sum) across numeric fields (like prices or quantities). The sample repository implements both in [`opensearch_search.py`](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101/blob/main/opensearch_search.py), with terms handling categorical faceting and stats powering metric dashboards.

### How do nested stats aggregations work in the sample repository?

**Nested stats aggregations** create a two-level analytical query that first buckets documents by a parent field (using a `terms` aggregation) and then calculates a metric (like `avg`, `sum`, or `max`) within each bucket. In [`opensearch_search.py`](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101/blob/main/opensearch_search.py) (lines 163-176), the code constructs a `terms` aggregation with a nested `aggs` clause containing the metric, enabling calculations like "average price per category" or "total inventory per manufacturer."

### Can I customize the range boundaries for bucket aggregations?

Yes, the **range aggregation** implementation in the sample repository accepts user-defined boundaries through the frontend configuration. In [`keyword-aggregations-page.tsx`](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101/blob/main/keyword-aggregations-page.tsx) (lines 116-124), the UI sends a `ranges` array specifying bucket boundaries (e.g., `[{"to": 50}, {"from": 50, "to": 100}, {"from": 100}]`), which [`opensearch_search.py`](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101/blob/main/opensearch_search.py) (lines 155-162) passes directly to the OpenSearch DSL `range` aggregation.

### What file handles the aggregation logic in the backend?

The aggregation logic resides in **[`artifacts/search_lambda/opensearch_search.py`](https://github.com/aws-samples/sample-for-amazon-opensearch-service-tutorials-101/blob/main/artifacts/search_lambda/opensearch_search.py)**, specifically within the `search_products` function (lines 141-176). This Python module receives aggregation requests from the frontend, constructs the appropriate OpenSearch DSL queries for terms, stats, range, and nested_stats aggregations, and returns the processed results to the React UI.