Skip to main content

Query Templates

Query templates enable dynamic and reusable query definitions. They allow you to create parameterized search request bodies, query string parameters, and HTTP headers to which the queries in a query set can be applied to generate requests for endpoints.

What are Query Templates?

Query templates are based on Mustache / Handlebars templating syntax, which supports:

  • Variable substitution using the {{variable}} syntax
  • Conditional logic, such as {{#if variable}}variable exists{{/if}}
  • Iteration over collections using {{#each collection}}{{/each}}

Anatomy of a Query Template

A query template consists of the following main properties:

  1. Body Template: Defines the structure of the HTTP request body
  2. Body Content Type: Defines the output content type of the body template
  3. Query String Template: Defines the URL query string parameters

Example

The following is an example of defining an Elasticsearch query with a query template.

Firstly, define the query string parameters

?size={{size}}&explain={{explain}}

Secondly, define the request body

{
"query": {
"match": {
"title": {
"query": "{{query}}",
"minimum_should_match": "{{minimum_should_match}}"
}
}
}
}

Putting this all together into a query template request:

{
"name": "Match Title",
"query_string": "?size={{size}}&explain={{explain}}",
"body": "{\"query\":{\"match\":{\"title\":{\"query\":\"{{query}}\",\"minimum_should_match\":\"{{minimum_should_match}}\"}}}}",
"content_type": "json"
}

In this example:

  • {{size}} and {{from}} are variables in the query string parameters for pagination
  • {{query}} is a variable in the body that will be replaced with the actual search term

Common Handlebars syntax

The following sections highlight the most common handlebars syntax you're likely to use. For more details, consult the Handlebars and Mustache documentation.

@data variables

@data variables are implemented by Handlebars and built-in helpers. They have values in specific contexts:

@data variabledescription
@rootInitial context with which the template is executed.
@firstSet to true by the {{#each}} helper for the first iteration
@lastSet to true by the {{#each}} helper for the last iteration
@indexSet to the zero-based index of the current iteration by the {{#each}} helper
@keySet to the key name of the current iteration by the {{#each}} helper when iterating over objects

Variables

A variable expression is a name enclosed within double braces. The most common is

{{query}}

which represents a query from a query set, as either a text string of search terms, or the "query" property in a query JSON object.

Variables are encoded/escaped based on the context:

  • In a query string, they are URL encoded
  • In a text request body, they are not encoded
  • In a JSON request body, they are JSON encoded

If you don't want a variable to be encoded/escaped, enclose it in triple braces

{{{query}}}

Conditions

If Condition

Branching logic can be expressed with the {{#if}} block helper function:

{{#if size}}size={{size}}{{/if}}

If size is a truthy value i.e. not false, undefined, null, "", 0 or [], then the value enclosed in the block will be rendered.

For query

{ "query": "The Godfather", "size": 20 }

this renders

size=20

but the following query

{ "query": "The Godfather" }

renders an empty string.

An else branch can be used to provide a default value, using {{else}} inside the body of an {{#if}} block:

size={{#if size}}{{size}}{{else}}10{{/if}}

which for the latter query, now renders

size=10

Unless Condition

The {{#unless}} condition can be considered the inverse of the {{#if}} condition; if the expression is a falsy value i.e. false, undefined, null, "", 0 or [], then the value enclosed in the block will be rendered.

The previous {{else}} branch could be written more verbosely as

size={{#if size}}{{size}}{{/if}}{{#unless size}}10{{/unless}}

Iteration

Iteration can be expressed with the {{#each}} block helper function:

{{#each field}}
{{this}}
{{/each}}

{{this}} inside the {{#each}} block represents the item in the current iteration.

Iteration can be used to template complex queries:

{
"query": {
"bool": {
"must": [
{{#each field}}
{
"match": {
"{{this}}": "{{@root.query}}"
}
}{{#unless @last}},{{/unless}}
{{/each}}
]
}
}
}

which for the following query

{ "query": "The Godfather", "field": ["title", "description", "actors"] }

renders

{
"query": {
"bool": {
"must": [
{
"match": {
"title": "the Godfather"
}
},
{
"match": {
"description": "the Godfather"
}
},
{
"match": {
"actors": "the Godfather"
}
}
]
}
}
}

toJSON block helper

The {{#toJson}} block helper function provides a powerful way to incorporate raw JSON into query templates. It works similarly to the Mustache helper found in Elasticsearch and OpenSearch, allowing JSON to be inserted directly into a query template without escaping. This is particularly useful when you need to include complex nested structures or arrays that would be cumbersome to construct through standard template syntax.

The name of the query property should be enclosed in the {{#toJson}} block to emit it as JSON:

{{#toJson}}filters{{/toJson}}

For example

{ "query": "The Godfather", "genre": ["Crime", "Drama"] }

You could use the toJson helper in your template like this:

{
"query": {
"bool": {
"should": [
{ "match": { "title": "{{query}}" } }
],
"filter": [
{ "terms": { "genres": {{#toJson}}genre{{/toJson}} } }
]
}
}
}

which renders

{
"query": {
"bool": {
"should": [
{ "match": { "title": "the Godfather" } }
],
"filter": [
{ "terms": { "genres": ["Crime","Drama"] } }
]
}
}
}