Unlocking the Power of Search Pipelines in OpenSearch
An OpenSearch search pipeline is a chain of processors that rewrites search requests and transforms results inside OpenSearch, so query logic lives in the cluster instead of your app.
An OpenSearch search pipeline is a named chain of processors that modifies a search request before it reaches the index and modifies the results before they return to the caller. It lets you move query rewriting, filtering, score normalization and result shaping out of your application and into OpenSearch, where you attach it with a single search_pipeline parameter.
As search applications grow, so does the logic around each query. You might need to restrict results by user, normalize scores for hybrid search, or rename fields for a client. Traditionally that logic lived in application code, which made the app heavy, hard to maintain, and tightly coupled to specific search logic. Search pipelines fix that.
What are search pipelines?
Think of a search pipeline as an assembly line for your search requests and responses. It is a modular framework that lets you chain together "processors" to modify queries before they hit the index and modify results before they return to the user.
This moves the heavy lifting out of your application and into OpenSearch itself, where it belongs.
What are the three stages of processing?
Search pipelines can intervene at three critical stages:
- Request processing: These processors intercept the incoming query. You can use them to:
- Add filters to every query (
filter_query). - Adjust request parameters such as
sizeorexplainwith a script. - Convert text to vectors for semantic search.
- Add filters to every query (
- Response processing: These processors handle the results after they are retrieved but before they are sent back. Use cases include:
- Renaming fields.
- Reranking or truncating hits.
- Search phase results processing: This stage runs on the coordinating node, between the query and fetch phases. It is essential for operations that need to see results from all shards together, such as the score normalization and rank fusion used in hybrid search.
Here is how a request moves through those stages:
flowchart LR
Q([Search request]) --> RQ[Request processors<br/>filter, adjust, embed]
RQ --> S[(Shards)]
S --> SP[Search phase results processors<br/>normalize scores across shards]
SP --> RS[Response processors<br/>rename fields, rerank]
RS --> R([Results])What are system-generated search processors?
System-generated search processors were introduced in OpenSearch 3.3. Unlike user-defined processors, which you configure in a pipeline yourself, OpenSearch creates these automatically when a request uses a feature that needs them.
Two things to know before relying on them:
- They are opt-in. You enable them with the
cluster.search.enabled_system_generated_factoriescluster setting, either*for all factories or an explicit list. - They run at fixed stages. Each one runs either before or after your user-defined processors of the same type, and OpenSearch allows only one system-generated processor per type and stage in a request, which keeps execution order deterministic.
The documented processors cover maximal marginal relevance (MMR) reranking (mmr_over_sample and mmr_rerank), excluding vector fields from _source by default (knn_default_excludes), and batch inference for the semantic highlighter.
PUT _cluster/settings
{
"persistent": {
"cluster.search.enabled_system_generated_factories": [
"mmr_over_sample_factory",
"mmr_rerank_factory",
"semantic-highlighter"
]
}
}
How do you build a search pipeline? A practical example
Say every query from a public-facing app must only return publicly visible documents. Instead of adding that filter in every backend call, define it once with the filter_query request processor:
PUT /_search/pipeline/public_only
{
"description": "Restricts results to publicly visible documents",
"request_processors": [
{
"filter_query": {
"query": {
"term": { "visibility": "public" }
}
}
}
]
}
Now you simply append ?search_pipeline=public_only to your search requests (or set it as the index's default pipeline). Your application code stays simple, and your search logic is centralized and testable.
A note on scripts: the script request processor can only change a fixed set of request fields (such as from, size, explain, track_total_hits and min_score), not the query itself. For query changes, reach for filter_query or another purpose-built processor.
How do you monitor search pipelines?
With great power comes the need for great observability. Pipeline metrics are exposed through the Nodes Stats API:
GET /_nodes/stats/search_pipeline
The response breaks down request and response counts, time spent and failures per pipeline and per processor, so you can see which processors are slow and debug misbehaving queries.
Conclusion
OpenSearch search pipelines are a tool every search engineer should have in their belt. They decouple logic from your app, power features like hybrid search, and, since 3.3, can attach the processors some features need automatically once you opt in.
If you are looking to optimize your search architecture, start by looking at what you can move into a pipeline. You might be surprised at how much cleaner your code becomes.
Sources
- OpenSearch documentation, System-generated search processors
- OpenSearch documentation, Filter query processor
- OpenSearch documentation, Script processor
- OpenSearch documentation, Search pipeline metrics
- OpenSearch documentation, Normalization processor
Abhilash B is an advisor at Query Quotient and an information retrieval and AI expert who holds patents in the field. Abhilash brings that retrieval depth to the search, RAG and agent systems Query Quotient builds for clients.
