Using Search Templates for Reusable Queries in Elasticsearch
AI generated
_doc
_index
Elasticsearch · Search Templates
Using Search Templates for Reusable Queries
separating query logic from parameters with Mustache templates

When the full Elasticsearch query DSL gets assembled directly in application code, search logic ends up scattered across many places in the codebase, and every adjustment to a central query requires changes in multiple spots at once. Search templates solve this by storing a Mustache-based query template once, centrally in Elasticsearch, after which application code only ever passes named parameters without needing to know a single line of query DSL. This moves the actual search logic to where it should technically be managed, and application developers work against a stable, clearly documented interface. How search templates are structured, what advantages this brings for collaboration between search teams and application teams, and what a parametrized product search looks like in practice is what this article covers.

11 min read Search Templates · Mustache _scripts API · Parametrization

1. The baseline problem: scattered query logic in application code

In many grown applications, the same or a very similar query DSL structure shows up in multiple places in the code, once for the frontend product search, once for an internal search service, and once for a batch export, each with small deviations. When the underlying relevance strategy changes, for instance because additional boosting for current offers gets introduced, all these spots need to be found and adjusted consistently, which in practice regularly leads to inconsistencies.

Search templates move the actual query structure out of application code into a template managed centrally in Elasticsearch. The application calls this template by name and only passes the concrete search parameters, while the entire structure of the query, including boosting rules, filters, and sorting, is maintained in a single place.

2. Mustache syntax: how placeholders work in the query DSL

A search template is, at its core, a regular query DSL structure in which individual values are replaced by Mustache placeholders like {{search_term}}. When the template runs, Elasticsearch substitutes every placeholder with the passed parameter value and evaluates the resulting, complete query exactly like a normal search request. Mustache also supports conditional blocks through {{#parameter}}...{{/parameter}}, which only insert a query component when the corresponding parameter was actually passed.

These conditional blocks are particularly valuable for optional filters: a price filter should only be added to the query when the user actually selected a price range, otherwise the query should run without that filter component. Without Mustache conditionals, the application itself would need to assemble multiple variants of the query, with Mustache a single template covers every combination of set and unset parameters.

3. Creating a search template through the _scripts API

Search templates get stored through the _scripts API, the same API also used for other stored scripts. Every template receives a unique name it is later referenced by, and a source block containing the actual query DSL structure including Mustache placeholders. Optionally, a params block with default values can also be defined, which applies whenever the application does not pass a particular parameter on a given call.

This central storage has a practical side effect: a change to the template takes effect immediately for every application referencing it, without any of those applications needing to be redeployed. For relevance tuning, which typically iterates more often than the actual application code, this is a significant speed advantage.


PUT _scripts/product_search_standard
{
  "script": {
    "lang": "mustache",
    "source": {
      "query": {
        "bool": {
          "must": [
            { "multi_match": { "query": "{{search_term}}", "fields": ["name^3", "description"] } }
          ],
          "filter": [
            {{#category}}{ "term": { "category": "{{category}}" } },{{/category}}
            { "range": { "price": { "gte": "{{price_min}}{{^price_min}}0{{/price_min}}", "lte": "{{price_max}}{{^price_max}}100000{{/price_max}}" } } }
          ]
        }
      },
      "size": "{{page_size}}{{^page_size}}20{{/page_size}}"
    },
    "params": { "page_size": 20 }
  }
}

4. Running a search template

A stored template gets executed through the _search/template endpoint, where the application only passes the id reference to the stored template and a params object with the concrete values. The response matches exactly the response of a regular search request, so existing response processing code can be reused without modification.

For local development and debugging, the _render/template endpoint is helpful, since it merely resolves a template with given parameters and returns the resulting, complete query DSL without actually executing a search. This makes it quick to verify that a template with certain parameters produces the expected query before running it against the index in production.


GET /products/_search/template
{
  "id": "product_search_standard",
  "params": {
    "search_term": "cordless drill",
    "category": "power_tools",
    "price_max": 89.90
  }
}

5. Benefits for application developers: no query DSL in application code

For teams providing a search interface to other application teams, a search template means a clear separation of responsibility: the search or data team maintains relevance, boosting, and filter logic centrally in the template, while application developers only need to know a named, documented parameter list, for instance search term, category, and price range. Changes to the relevance strategy no longer require coordination with every single application team, as long as the parameter interface itself does not change.

This also reduces the risk of faulty query DSL construction in application code, which typically is not specialized in Elasticsearch query syntax. Instead of debugging nested bool structures scattered through application code, a single look at the central template, maintained by people who actually know the query DSL, suffices.

6. Versioning and controlled changes to templates

Since changes to a search template take effect immediately and without deployment for every calling application, a deliberate versioning strategy through the template name itself is advisable, for instance product_search_v2 instead of directly overwriting product_search_standard. This lets new application versions deliberately switch to the new template version, while existing callers keep working unchanged until they are explicitly migrated.

For controlled rollouts, an A/B testing approach is also useful, where a portion of the traffic gets routed to a new template on a trial basis while the majority still uses the proven template. Since both templates are stored independently in _scripts, this approach can be steered purely through the called template ID, without any application code change.

7. Performance: compilation and caching of templates

Elasticsearch compiles a stored template on its first call and keeps the compiled form in an internal script cache, so repeated calls to the same template do not require recompiling the Mustache template again. The actual execution overhead of a search template therefore practically does not differ from a directly formulated, equivalent query, the additional cost is limited to the one-time parameter substitution per call.

For very complex templates with many conditional blocks and deeply nested logic, it is still worth watching the size of the script cache, especially when many different templates are in use in parallel. The cache can be adjusted through script.cache.max_size if frequently used templates get evicted from the cache and therefore need to be recompiled repeatedly.

In the shown product search template, the search term is checked mandatorily through multi_match against name and description, while category and price range remain optional. If the price filter is missing entirely, sensible defaults kick in through the Mustache negation syntax {{^price_min}}, so the query contains a valid, sensible range condition even without an explicit price given, instead of failing with an empty or invalid filter.

The application itself only needs to pass the user-entered values as parameters, without knowing that a bool query with multi_match, a conditional term filter, and a range filter is being assembled behind the scenes. This separation remains stable even when the search team later adds, say, additional function score boosting for stock levels, as long as the parameter interface itself does not change.

9. Pitfalls and recommendations for production use

A common mistake is inserting parameters into a template unchecked and without escaping, especially for free-text fields that could theoretically contain Mustache special characters. Elasticsearch escapes parameter values inside Mustache expressions correctly by default, it only becomes problematic when parameters are accidentally inserted as raw query rather than as a value, which can blur the line between parametrization and potential query injection.

It is also advisable to back every production template with a small test suite of _render/template calls covering typical and edge cases of parameter combinations, for instance missing optional parameters, extreme price ranges, or empty search terms. This catches regressions from template changes early, before they show up in production against real search requests.

Aspect Query DSL in application code Search Template Practical relevance
Where query logic lives Scattered across codebases Centralized in _scripts Templates prevent inconsistencies
Deployment on changes New application deployment required Effective immediately, no deployment Faster relevance iteration
Domain knowledge in application code Query DSL knowledge required Only named parameters needed Clear separation of responsibility
Debugging Through application logs Directly verifiable via _render/template Faster troubleshooting of templates
Optional filters Manual branching in code Mustache conditional blocks One template for every parameter combination

Mironsoft

Search index setup, relevance tuning, and Magento search

Magento search that shows the wrong products first?

We set up Elasticsearch or OpenSearch for Magento cleanly, tune relevance and facets to the actual catalog, and optimize indexing processes for large catalogs.

Relevance Tuning

Match search results and facets to actual customer needs.

Search Migration

Guide a clean migration from Solr or MySQL search to Elasticsearch/OpenSearch.

Index Performance

Make indexing processes for large catalogs reliable and performant.

10. Summary

Search Templates: The Essentials at a Glance

Core principle

Search templates separate the Mustache-based query DSL structure from the concrete search parameters, which an application only ever passes by name.

Management

Templates are stored centrally through the _scripts API and executed via _search/template with parameters, with no application code change needed for query adjustments.

Benefit for teams

Application developers only know a documented parameter interface, while the search team evolves relevance and filter logic independently.

Practical example

A parametrized product search with optional category and price filters shows how Mustache conditional blocks make a single template viable for every parameter combination.

11. FAQ: Search Templates: The Essentials at a Glance

1What is a search template in Elasticsearch?
A centrally stored, Mustache-based query DSL template in which individual values are replaced by placeholders, and which gets called by name with concrete parameters.
2Where are search templates stored?
Through the _scripts API, the same API also used for other stored scripts in Elasticsearch, with lang set to mustache.
3How do you run a stored search template?
Through the _search/template endpoint, passing the id of the stored template and a params object containing the concrete values for the placeholders.
4What benefit do search templates bring for application developers?
They do not need to know query DSL details, only a named, documented parameter interface, while the actual query logic is maintained centrally.
5How can optional filters be modeled in a template?
Through Mustache conditional blocks like {{#category}}...{{/category}}, which only insert a query component when the corresponding parameter was actually passed.
6How can a template be debugged without triggering a real search?
Through the _render/template endpoint, which only resolves a template with given parameters and returns the resulting query DSL without actually searching.
7Do changes to a template take effect immediately for every application?
Yes, as soon as a template gets updated through the _scripts API, the changes apply to every following call without any application code needing to be redeployed.
8Why should templates be versioned instead of overwritten directly?
Because overwriting immediately affects every calling application. A separate name per version allows a controlled rollout while existing callers keep working unchanged.
9Do search templates cause additional performance overhead?
Only minimally, Elasticsearch compiles templates once and keeps them in the script cache, after which execution cost essentially matches a directly formulated query.
10What should you watch out for when using templates in production?
Correct escaping of parameter values and a test suite of _render/template calls that cover typical and edge case parameter combinations before every production rollout.