How to Decouple Elasticsearch Queries Using Mustache Search Templates
This article explains how to separate Elasticsearch DSL queries from application code by defining reusable search templates with Mustache placeholders, managing them via the scripts API, and rendering them with parameters to produce flexible, maintainable search requests.
Search Template Overview
A search template is a reusable Mustache script that defines an Elasticsearch query DSL with {{}} placeholders. It separates the DSL definition from application code, allowing the query structure to be changed without modifying the client.
Workflow
Define the DSL structure and reserve placeholder parameters.
Provide concrete values for those parameters at query time.
Render the full DSL and execute the search.
Search Template API
Templates are stored and managed through the _scripts endpoint.
Save a template
POST _scripts/<template_id>
{
"script": {
"lang": "mustache",
"source": {
"query": {
"match": {
"{{my_field}}": "{{my_value}}"
}
}
}
}
}Get a template
GET _scripts/<template_id>Delete a template
DELETE _scripts/<template_id>Execute a template
Supply the template id and a params object whose keys match the placeholders.
GET _search/template
{
"id": "<template_id>",
"params": {
"my_field": "message",
"my_value": "foo"
}
}The response contains the rendered DSL, e.g.:
{
"query": {
"match": {
"message": "foo"
}
}
}Render a template for inspection
GET _render/template
{
"source": "{ \"query\": { \"terms\": {{#toJson}}statuses{{/toJson}} } }",
"params": {
"statuses": { "status": ["pending", "published"] }
}
} {
"template_output": {
"query": {
"terms": {
"status": ["pending", "published"]
}
}
}
}Mustache Helpers
{{#toJson}}parameter{{/toJson}}– renders a parameter as JSON. {{#join}}array{{/join}} – joins an array into a comma‑separated string (default delimiter is ,). {{#join delimiter='||'}}array{{/join}} – joins with a custom delimiter. {{= <% %> =}} – changes the delimiters for a section. {{var}}{{^var}}default{{/var}} – provides a default value when var is missing or falsy. {{#key}}…{{/key}} – renders the block only when key is truthy. {{^key}}…{{/key}} – renders the block only when key is falsy or absent. {{! comment }} – comment that is ignored. {{> partial}} – includes another stored template. {{#url}}value{{/url}} – URL‑encodes the value.
Practical Examples
Simple value substitution
GET _search/template
{
"source": {
"query": { "term": { "message": "{{query_string}}" } }
},
"params": { "query_string": "search words" }
}Joining an array
GET _search/template
{
"source": {
"query": { "match": { "emails": "{{#join}}emails{{/join}}" } }
},
"params": { "emails": ["aaa", "bbb"] }
}Resulting DSL:
{
"query": {
"match": { "emails": "aaa,bbb" }
}
}Using toJson for complex objects
GET _search/template
{
"source": "{ \"query\": { \"terms\": {{#toJson}}statuses{{/toJson}} } }",
"params": {
"statuses": { "status": ["pending", "published"] }
}
}Conditional sections and defaults
{
"source": {
"query": {
"range": {
"born": {
"gte": "{{date.min}}",
"lte": "{{date.max}}",
"format": "{{#join delimiter='||'}}date.formats{{/join}}"
}
}
}
},
"params": {
"date": {
"min": "2016",
"max": "31/12/2017",
"formats": ["dd/MM/yyyy", "yyyy"]
}
}
}Rendered format value becomes dd/MM/yyyy||yyyy.
Default numeric values
{
"source": {
"query": {
"range": {
"line_no": {
"gte": "{{start}}",
"lte": "{{end}}{{^end}}20{{/end}}"
}
}
}
},
"params": { "start": "10" }
}If end is omitted, the rendered DSL uses 20 as the upper bound.
URL encoding
GET _render/template
{
"source": {
"query": { "term": { "http_access_log": "{{#url}}{{host}}/{{page}}{{/url}}" } }
},
"params": { "host": "https://www.elastic.co/", "page": "learn" }
} {
"template_output": {
"query": {
"term": { "http_access_log": "https%3A%2F%2Fwww.elastic.co%2F%2Flearn" }
}
}
}Mustache Syntax Quick Reference
{{key}}– inserts the value of key (HTML‑escaped). {{{key}}} or {{&key}} – inserts without escaping. {{#section}}…{{/section}} – renders the block when section is truthy. {{^section}}…{{/section}} – renders the block when section is falsy or missing. {{! comment }} – comment. {{> partial}} – includes another template. {{= <% %> =}} – changes delimiters for the following sections.
Conclusion
Search templates let developers keep application code focused on parameters and results while delegating DSL construction to reusable Mustache scripts, improving maintainability and flexibility of Elasticsearch queries.
System Architect Go
Programming, architecture, application development, message queues, middleware, databases, containerization, big data, image processing, machine learning, AI, personal growth.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
