[[search-template]]
== Search templates

A search template is a stored search you can run with different variables.

If you use {es} as a search backend, you can pass user input from a search bar
as parameters for a search template. This lets you run searches without exposing
{es}'s query syntax to your users.

If you use {es} for a custom application, search templates let you change
your searches without modifying your app's code.

[discrete]
[[create-search-template]]
=== Create a search template

To create or update a search template, use the <<create-stored-script-api,create
stored script API>>.

The request's `source` supports the same parameters as the
<<search-search-api-request-body,search API>>'s request body. `source` also
supports https://mustache.github.io/[Mustache] variables, typically enclosed in
double curly brackets: `{{my-var}}`. When you run a templated search, {es}
replaces these variables with values from `params`.

Search templates must use a `lang` of `mustache`.

The following request creates a search template with an `id` of
`my-search-template`.

[source,console]
----
PUT _scripts/my-search-template
{
  "script": {
    "lang": "mustache",
    "source": {
      "query": {
        "match": {
          "message": "{{query_string}}"
        }
      },
      "from": "{{from}}",
      "size": "{{size}}"
    },
    "params": {
      "query_string": "My query string"
    }
  }
}
----

{es} stores search templates as Mustache <<modules-scripting,scripts>> in the
cluster state. {es} compiles search templates in the `template` script context.
Settings that limit or disable scripts also affect search templates.

[discrete]
[[validate-search-template]]
=== Validate a search template

[[_validating_templates]]
To test a template with different `params`, use the
<<render-search-template-api,render search template API>>.

[source,console]
----
POST _render/template
{
  "id": "my-search-template",
  "params": {
    "query_string": "hello world",
    "from": 20,
    "size": 10
  }
}
----
// TEST[continued]

When rendered, the template outputs a <<search-search-api-request-body,search
request body>>.

[source,console-result]
----
{
  "template_output": {
    "query": {
      "match": {
        "message": "hello world"
      }
    },
    "from": "20",
    "size": "10"
  }
}
----

You can also use the API to test inline templates.

[source,console]
----
POST _render/template
{
    "source": {
      "query": {
        "match": {
          "message": "{{query_string}}"
        }
      },
      "from": "{{from}}",
      "size": "{{size}}"
    },
  "params": {
    "query_string": "hello world",
    "from": 20,
    "size": 10
  }
}
----
// TEST[continued]

[discrete]
[[run-templated-search]]
=== Run a templated search

To run a search with a search template, use the <<search-template-api,search
template API>>. You can specify different `params` with each request.

////
[source,console]
----
PUT my-index/_doc/1?refresh
{
  "message": "hello world"
}
----
// TEST[continued]
////

[source,console]
----
GET my-index/_search/template
{
  "id": "my-search-template",
  "params": {
    "query_string": "hello world",
    "from": 0,
    "size": 10
  }
}
----
// TEST[continued]

The response uses the same properties as the <<search-search,search API>>'s
response.

[source,console-result]
----
{
  "took": 36,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 0.5753642,
    "hits": [
      {
        "_index": "my-index",
        "_type": "_doc",
        "_id": "1",
        "_score": 0.5753642,
        "_source": {
          "message": "hello world"
        }
      }
    ]
  }
}
----
// TESTRESPONSE[s/"took": 36/"took": "$body.took"/]

[discrete]
[[run-multiple-templated-searches]]
=== Run multiple templated searches

To run multiple templated searches with a single request, use the
<<multi-search-template,multi search template API>>. These requests often have
less overhead and faster speeds than multiple individual searches.

[source,console]
----
GET my-index/_msearch/template
{ }
{ "id": "my-search-template", "params": { "query_string": "hello world", "from": 0, "size": 10 }}
{ }
{ "id": "my-other-search-template", "params": { "query_type": "match_all" }}
----
// TEST[continued]
// TEST[s/my-other-search-template/my-search-template/]

[discrete]
[[get-search-templates]]
=== Get search templates

To retrieve a search template, use the <<get-stored-script-api,get stored
script API>>.

[source,console]
----
GET _scripts/my-search-template
----
// TEST[continued]

To get a list of all search templates and other stored scripts, use the
<<cluster-state,cluster state API>>.

[source,console]
----
GET _cluster/state/metadata?pretty&filter_path=metadata.stored_scripts
----
// TEST[continued]

[discrete]
[[delete-search-template]]
=== Delete a search template

To delete a search template, use the <<delete-stored-script-api,delete stored
script API>>.

[source,console]
----
DELETE _scripts/my-search-template
----
// TEST[continued]

[discrete]
[[search-template-set-default-values]]
=== Set default values

To set a default value for a variable, use the following syntax:

[source,mustache]
----
{{my-var}}{{^my-var}}default value{{/my-var}}
----

If a templated search doesn't specify a value in its `params`, the search uses
the default value instead. For example, the following template sets defaults
for `from` and `size`.

[source,console]
----
POST _render/template
{
  "source": {
    "query": {
      "match": {
        "message": "{{query_string}}"
      }
    },
    "from": "{{from}}{{^from}}0{{/from}}",
    "size": "{{size}}{{^size}}10{{/size}}"
  },
  "params": {
    "query_string": "hello world"
  }
}
----

[discrete]
[[search-template-url-encode-strings]]
=== URL encode strings

Use the `{{#url}}` function to URL encode a string.

[source,console]
----
POST _render/template
{
  "source": {
    "query": {
      "term": {
        "url.full": "{{#url}}{{host}}/{{page}}{{/url}}"
      }
    }
  },
  "params": {
    "host": "http://example.com",
    "page": "hello-world"
  }
}
----

The template renders as:

[source,console-result]
----
{
  "template_output": {
    "query": {
      "term": {
        "url.full": "http%3A%2F%2Fexample.com%2Fhello-world"
      }
    }
  }
}
----

[discrete]
[[search-template-concatenate-values]]
=== Concatenate values

Use the `{{#join}}` function to concatenate array values as a comma-delimited
string. For example, the following template concatenates two email addresses.

[source,console]
----
POST _render/template
{
  "source": {
    "query": {
      "match": {
        "user.group.emails": "{{#join}}emails{{/join}}"
      }
    }
  },
  "params": {
    "emails": [ "user1@example.com", "user_one@example.com" ]
  }
}
----

The template renders as:

[source,console-result]
----
{
  "template_output": {
    "query": {
      "match": {
        "user.group.emails": "user1@example.com,user_one@example.com"
      }
    }
  }
}
----

You can also specify a custom delimiter.

[source,console]
----
POST _render/template
{
  "source": {
    "query": {
      "range": {
        "user.effective.date": {
          "gte": "{{date.min}}",
          "lte": "{{date.max}}",
          "format": "{{#join delimiter='||'}}date.formats{{/join delimiter='||'}}"
	      }
      }
    }
  },
  "params": {
    "date": {
      "min": "2098",
      "max": "06/05/2099",
      "formats": ["dd/MM/yyyy", "yyyy"]
    }
  }
}
----

The template renders as:

[source,console-result]
----
{
  "template_output": {
    "query": {
      "range": {
        "user.effective.date": {
          "gte": "2098",
          "lte": "06/05/2099",
          "format": "dd/MM/yyyy||yyyy"
        }
      }
    }
  }
}
----

[discrete]
[[search-template-convert-json]]
=== Convert to JSON

Use the `{{#toJson}}` function to convert a variable value to its JSON
representation.

For example, the following template uses `{{#toJson}}` to pass an array. To
ensure the request body is valid JSON, the `source` is written in the string
format.

[source,console]
----
POST _render/template
{
  "source": "{ \"query\": { \"terms\": { \"tags\": {{#toJson}}tags{{/toJson}} }}}",
  "params": {
    "tags": [
      "prod",
      "es01"
    ]
  }
}
----

The template renders as:

[source,console-result]
----
{
  "template_output": {
    "query": {
      "terms": {
        "tags": [
          "prod",
          "es01"
        ]
      }
    }
  }
}
----

You can also use `{{#toJson}}` to pass objects.

[source,console]
----
POST _render/template
{
  "source": "{ \"query\": {{#toJson}}my_query{{/toJson}} }",
  "params": {
    "my_query": {
      "match_all": { }
    }
  }
}
----

The template renders as:

[source,console-result]
----
{
  "template_output" : {
    "query" : {
      "match_all" : { }
    }
  }
}

----

You can also pass an array of objects.

[source,console]
----
POST _render/template
{
  "source": "{ \"query\": { \"bool\": { \"must\": {{#toJson}}clauses{{/toJson}} }}}",
  "params": {
    "clauses": [
      {
        "term": {
          "user.id": "kimchy"
        }
      },
      {
        "term": {
          "url.domain": "example.com"
        }
      }
    ]
  }
}
----

The template renders as:

[source,console-result]
----
{
  "template_output": {
    "query": {
      "bool": {
        "must": [
          {
            "term": {
              "user.id": "kimchy"
            }
          },
          {
            "term": {
              "url.domain": "example.com"
            }
          }
        ]
      }
    }
  }
}
----

[discrete]
[[search-template-use-conditions]]
=== Use conditions

To create if conditions, use the following syntax:

[source,mustache]
----
{{#condition}}content{{/condition}}
----

If the condition variable is `true`, {es} displays its content. For example, the
following template searches data from the past year if `year_scope` is `true`.

[source,console]
----
POST _render/template
{
  "source": "{ \"query\": { \"bool\": { \"filter\": [ {{#year_scope}} { \"range\": { \"@timestamp\": { \"gte\": \"now-1y/d\", \"lt\": \"now/d\" } } }, {{/year_scope}} { \"term\": { \"user.id\": \"{{user_id}}\" }}]}}}",
  "params": {
    "year_scope": true,
    "user_id": "kimchy"
  }
}
----

The template renders as:

[source,console-result]
----
{
  "template_output" : {
    "query" : {
      "bool" : {
        "filter" : [
          {
            "range" : {
              "@timestamp" : {
                "gte" : "now-1y/d",
                "lt" : "now/d"
              }
            }
          },
          {
            "term" : {
              "user.id" : "kimchy"
            }
          }
        ]
      }
    }
  }
}
----

If `year_scope` is `false`, the template searches data from any time period.

[source,console]
----
POST _render/template
{
  "source": "{ \"query\": { \"bool\": { \"filter\": [ {{#year_scope}} { \"range\": { \"@timestamp\": { \"gte\": \"now-1y/d\", \"lt\": \"now/d\" } } }, {{/year_scope}} { \"term\": { \"user.id\": \"{{user_id}}\" }}]}}}",
  "params": {
    "year_scope": false,
    "user_id": "kimchy"
  }
}
----

The template renders as:

[source,console-result]
----
{
  "template_output" : {
    "query" : {
      "bool" : {
        "filter" : [
          {
            "term" : {
              "user.id" : "kimchy"
            }
          }
        ]
      }
    }
  }
}
----

To create if-else conditions, use the following syntax:

[source,mustache]
----
{{#condition}}if content{{/condition}} {{^condition}}else content{{/condition}}
----

For example, the following template searches data from the past year if
`year_scope` is `true`. Otherwise, it searches data from the past day.

[source,console]
----
POST _render/template
{
  "source": "{ \"query\": { \"bool\": { \"filter\": [ { \"range\": { \"@timestamp\": { \"gte\": {{#year_scope}} \"now-1y/d\" {{/year_scope}} {{^year_scope}} \"now-1d/d\" {{/year_scope}} , \"lt\": \"now/d\" }}}, { \"term\": { \"user.id\": \"{{user_id}}\" }}]}}}",
  "params": {
    "year_scope": true,
    "user_id": "kimchy"
  }
}
----
