# How to Implement Range Filters for Numeric and Date Fields in OpenSearch

> Learn how to implement range filters for numeric and date fields in OpenSearch. Construct JSON query clauses with gt, gte, lt, and lte operators for powerful data filtering.

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

---

**Range filters in OpenSearch are implemented by constructing JSON query clauses using operators like `gt`, `gte`, `lt`, and `lte`, which the Lambda handler in `aws-samples/sample-for-amazon-opensearch-service-tutorials-101` translates from HTTP request parameters into OpenSearch DSL.**

The `aws-samples/sample-for-amazon-opensearch-service-tutorials-101` repository provides a complete reference implementation showing how to build serverless search APIs that handle numeric and date range filtering. Understanding these patterns helps you construct efficient queries for price ranges, date intervals, and other bounded numeric data in OpenSearch.

## Simple Range Filters for Single-Field Queries

The repository implements a straightforward `range_filter` search type designed for single-field numeric or date constraints. This approach extracts operator and value information directly from the incoming request payload.

### How the Lambda Handler Processes Range Operators

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) (lines 68-78), the `search_products` function handles the `range_filter` type by validating the operator and converting values:

```python

# From artifacts/search_lambda/opensearch_search.py lines 68-78

if search_type == "range_filter":
    attribute_name = body.get("attribute_name")
    attribute_value = body.get("attribute_value")
    operator = body.get("operator")  # gt, gte, lt, lte

    
    # Validate operator is a string

    if not isinstance(operator, str):
        raise ValueError("Operator must be a string")
    
    # Convert to integer for numeric fields

    value = int(attribute_value)
    
    # Build the range query

    search_body = {
        "query": {
            "range": {
                attribute_name: {
                    operator: value
                }
            }
        }
    }

```

The code validates that the operator is one of the supported string values (`gt`, `gte`, `lt`, `lte`), converts the attribute value to an integer for numeric comparison, and nests the operator under the field name within the OpenSearch `range` clause.

### Example: Filtering Products by Price

When a client sends a request to filter products with prices greater than or equal to 100:

```json
{
  "type": "range_filter",
  "attribute_name": "price",
  "attribute_value": "150",
  "operator": "gte"
}

```

The Lambda generates the following OpenSearch query:

```json
{
  "size": 100,
  "query": {
    "range": {
      "price": { "gte": 150 }
    }
  }
}

```

## Complex Multi-Field Range Filtering

For scenarios requiring multiple field constraints or more sophisticated date ranges, the repository implements a complex search handler that processes field definitions with explicit `min` and `max` boundaries.

### Building Range Queries with Min/Max Values

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) (lines 31-38), the code handles fields where `type` equals `"range"`:

```python

# From artifacts/search_lambda/opensearch_search.py lines 31-38

for field in fields:
    field_name = field.get("name")
    field_type = field.get("type")
    field_value = field.get("value")
    
    if field_type == "range":
        range_query = {"range": {field_name: {}}}
        
        if "min" in field_value:
            range_query["range"][field_name]["gte"] = field_value["min"]
        if "max" in field_value:
            range_query["range"][field_name]["lte"] = field_value["max"]
            
        must_clauses.append(range_query)

```

This implementation checks for the presence of `min` and `max` keys in the field value object, mapping them to `gte` (greater than or equal) and `lte` (less than or equal) operators respectively. This approach works for both numeric fields and date strings.

### Example: Numeric Rating Range

For filtering products with ratings between 3 and 5:

```json
{
  "type": "complex_search",
  "search_type": "combined",
  "fields": [
    {
      "name": "rating",
      "type": "range",
      "value": { "min": 3, "max": 5 }
    }
  ]
}

```

The generated OpenSearch query fragment:

```json
{
  "range": {
    "rating": {
      "gte": 3,
      "lte": 5
    }
  }
}

```

### Example: Date Range Filtering

For date fields, the same min/max pattern applies using ISO date strings:

```json
{
  "type": "complex_search",
  "search_type": "combined",
  "fields": [
    {
      "name": "release_date",
      "type": "range",
      "value": { 
        "min": "2022-01-01", 
        "max": "2022-12-31" 
      }
    }
  ]
}

```

This produces:

```json
{
  "range": {
    "release_date": {
      "gte": "2022-01-01",
      "lte": "2022-12-31"
    }
  }
}

```

## Summary

- **Simple range filters** use the `range_filter` search type 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) (lines 68-78), supporting `gt`, `gte`, `lt`, and `lte` operators for single-field numeric queries.
- **Complex range filters** handle multi-field scenarios using the `"range"` field type (lines 31-38), mapping `min` values to `gte` and `max` values to `lte` for both numeric and date fields.
- Both implementations construct standard OpenSearch DSL `range` queries that execute via `ops_client.search()`, leveraging OpenSearch's native support for numeric and date range filtering.
- The repository demonstrates production-ready patterns for validating operator strings, converting data types, and building dynamic query clauses in Python Lambda functions.

## Frequently Asked Questions

### How do I filter documents by a numeric price range in OpenSearch using this sample?

Send a POST request with `type: "range_filter"` including `attribute_name` (the field), `attribute_value` (the number as a string), and `operator` (`gte`, `lte`, `gt`, or `lt`). The Lambda 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) converts the value to an integer and builds the OpenSearch `range` query with your specified operator.

### Can I use date strings with the range filter implementation?

Yes. While the simple `range_filter` type converts values to integers for numeric fields, the complex search implementation (lines 31-38) accepts date strings in ISO format (e.g., `"2023-01-01"`) when using the `"range"` field type with `min` and `max` values. OpenSearch automatically handles date parsing based on your index mappings.

### What is the difference between the simple range_filter and the complex range field type?

The **simple `range_filter`** (lines 68-78) is designed for single-field queries with explicit operators (`gt`, `gte`, `lt`, `lte`) and converts values to integers. The **complex `"range"` field type** (lines 31-38) supports multi-field search requests, uses `min`/`max` semantics instead of explicit operators, and preserves string values for dates or decimals without forced integer conversion.

### Which operators are supported for range queries in this implementation?

The implementation supports the standard OpenSearch range operators: **gt** (greater than), **gte** (greater than or equal), **lt** (less than), and **lte** (less than or equal). In the simple filter, these are passed directly in the request. In the complex search, `min` maps to `gte` and `max` maps to `lte` automatically.