Introducing Elasticsearch Query Rules UI in Kibana

Learn how to use the Elasticsearch Query Rules UI to add or exclude documents from search queries using customizable rulesets in Kibana, without affecting organic ranking.

A search engine’s job is to return relevant results. However, there are business needs that go beyond that,–like highlighting sales, prioritizing seasonal products, or showcasing sponsored items—and developers cannot always do this in the search query.

In addition, these use cases are usually time-sensitive, and going through the typical development stages (creating a code branch and then waiting for a new release) is a time-consuming process.

So, what if we could do this whole process with just an API call, or better yet, with just a few clicks in Kibana?

Query Rules UI

Elasticsearch 8.10 introduced Query Rules and Rule Retriever. These are tools designed to inject pinned results into the queries without impacting the organic results’ ranking based on rules. They only add business logic on top of the results in a declarative and simple way.

Some common use cases for Query Rules are:

  • Highlighting promoted listings or sales: Showing on-sale or sponsored items on top.
  • Excluding by context or geolocation: Hiding certain items when local regulations don’t allow you to show them.
  • Prioritizing key results: Making sure popular or fixed searches are always on top, regardless of the organic ranking.

To access the interface and interact with these tools, you need to click on the Kibana side menu and go to Query Rules, under Relevance:

Once the query rules menu pops up, click on Create your first ruleset:

Next, you need to name your ruleset.

The form to define each rule has three key components:

  • Criteria: The conditions that must be fulfilled for the rule to apply. For example, “when the query_string field contains the value Christmas” or “when the country field is CO.”
  • Action: This is what you want to happen when the conditions are met. It can be pinned (fixing a document to the top results) or excluded (hiding a document).
  • Metadata: These are the fields that go with the query when it runs. They can include the user’s information (like location or language) as well as search data (query_string). These are the values used by the criteria to decide whether or not to apply a rule.

Let’s imagine that we have an e-commerce site with different items. When checking out the metrics, we notice that one of the most sold items in the console category is the “DualShock 4 Wireless Controller,” especially when users search for the keywords “PS4” or “PlayStation 4”. So, we decide to put this product on top of the results, whenever a user searches for those keywords.

First, let’s index the documents for each item using a Bulk API request:

POST _bulk
{ "index": { "_index": "products", "_id": "1" } }
{ "id": "1", "name": "PlayStation 4 Slim 1TB", "category": "console", "brand": "Sony", "price": 1200 }
{ "index": { "_index": "products", "_id": "2" } }
{ "id": "2", "name": "DualShock 4 Wireless Controller", "category": "accessory", "brand": "Sony", "price": 250 }
{ "index": { "_index": "products", "_id": "3" } }
{ "id": "3", "name": "PlayStation 4 Camera", "category": "accessory", "brand": "Sony", "price": 200 }
{ "index": { "_index": "products", "_id": "4" } }
{ "id": "4", "name": "PlayStation 4 VR Headset", "category": "accessory", "brand": "Sony", "price": 900 }
{ "index": { "_index": "products", "_id": "5" } }
{ "id": "5", "name": "Charging Station for DualShock 4", "category": "accessory", "brand": "Sony", "price": 80 }

If we don’t intervene in the query, the item usually appears in the fourth place. Here’s the query:

GET products/_search
{
 "query": {
   "match": {
     "name": "PlayStation 4"
   }
 }
}

And here are the results

{
 "took": 1,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 5,
     "relation": "eq"
   },
   "max_score": 0.6973252,
   "hits": [
     {
       "_index": "products",
       "_id": "3",
       "_score": 0.6973252,
       "_source": {
         "id": "3",
         "name": "PlayStation 4 Camera",
         "category": "accessory",
         "brand": "Sony",
         "price": 200
       }
     },
     {
       "_index": "products",
       "_id": "1",
       "_score": 0.6260078,
       "_source": {
         "id": "1",
         "name": "PlayStation 4 Slim 1TB",
         "category": "console",
         "brand": "Sony",
         "price": 1200
       }
     },
     {
       "_index": "products",
       "_id": "4",
       "_score": 0.6260078,
       "_source": {
         "id": "4",
         "name": "PlayStation 4 VR Headset",
         "category": "accessory",
         "brand": "Sony",
         "price": 900
       }
     },
     {
       "_index": "products",
       "_id": "2",
       "_score": 0.08701137,
       "_source": {
         "id": "2",
         "name": "DualShock 4 Wireless Controller",
         "category": "accessory",
         "brand": "Sony",
         "price": 250
       }
     },
     {
       "_index": "products",
       "_id": "5",
       "_score": 0.07893815,
       "_source": {
         "id": "5",
         "name": "Charging Station for DualShock 4",
         "category": "accessory",
         "brand": "Sony",
         "price": 80
       }
     }
   ]
 }
}

Let’s create a query rule to change this. First, let’s add it to the ruleset like this:

Or equivalent API request:

PUT _query_rules/my-rules
{
  "rules": [
    {
      "rule_id": "rule-1232",
      "type": "pinned",
      "criteria": [
        {
          "type": "exact",
          "metadata": "query_string",
          "values": [
            "PS4",
            "PlayStation 4"
          ]
        }
      ],
      "actions": {
        "docs": [
          {
            "_index": "products",
            "_id": "2"
          }
        ]
      }
    }
  ]
}

To use the ruleset in our query, we must use a query rule type. This kind of query is made up of two main parts:

GET /products/_search
{
 "retriever": {
   "rule": {
     "retriever": {
       "standard": {
         "query": {
           "match": { "name": "PlayStation 4" }
         }
       }
     },
     "match_criteria": {
       "query_string": "PlayStation 4"
     },
     "ruleset_ids": ["my-rules"]
   }
 }
}
  • match_criteria: These are the metadata used to compare against the user’s query. In this example, the ruleset is activated when the query_string field has the value “PlayStation 4.”
  • query: the actual query that will be used to search and get the organic results.

This way, you first run the organic query, and then Elasticsearch applies the rules from your rule set:

{
 "took": 17,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 5,
     "relation": "eq"
   },
   "max_score": 1.7014122e+38,
   "hits": [
     {
       "_index": "products",
       "_id": "2",
       "_score": 1.7014122e+38,
       "_source": {
         "id": "2",
         "name": "DualShock 4 Wireless Controller",
         "category": "accessory",
         "brand": "Sony",
         "price": 250
       }
     },
     {
       "_index": "products",
       "_id": "3",
       "_score": 0.6973252,
       "_source": {
         "id": "3",
         "name": "PlayStation 4 Camera",
         "category": "accessory",
         "brand": "Sony",
         "price": 200
       }
     },
     {
       "_index": "products",
       "_id": "1",
       "_score": 0.6260078,
       "_source": {
         "id": "1",
         "name": "PlayStation 4 Slim 1TB",
         "category": "console",
         "brand": "Sony",
         "price": 1200
       }
     },
     {
       "_index": "products",
       "_id": "4",
       "_score": 0.6260078,
       "_source": {
         "id": "4",
         "name": "PlayStation 4 VR Headset",
         "category": "accessory",
         "brand": "Sony",
         "price": 900
       }
     },
     {
       "_index": "products",
       "_id": "5",
       "_score": 0.07893815,
       "_source": {
         "id": "5",
         "name": "Charging Station for DualShock 4",
         "category": "accessory",
         "brand": "Sony",
         "price": 80
       }
     }
   ]
 }
}

Example: user-based metadata

Another interesting application of Query Rules is to use metadata to show specific documents based on contextual information from the user or the webpage.

For example, let’s pretend that we want to highlight items or customized sales based on a user’s loyalty level, represented as a numeric value.

We can do this by ingesting this metadata directly into the query so that the rules activate when said value meets certain criteria.

First, we’ll index a document that only users with a high loyalty level can see:

POST _bulk
{ "index": { "_index": "products", "_id": "6" } }
{ "id": "6", "name": "PlayStation Plus Deluxe Card - 12 months", "category": "membership", "brand": "Sony", "price": 300 }

Now, let’s create a new rule inside the same ruleset so that when the loyalty_level is equal to or higher than 80, the item will appear on top of the results.

Save the rule and the ruleset.

Here is the equivalent REST request:

PUT _query_rules/my-rules
{
  "rules": [
    {
      "rule_id": "pin-premiun-user",
      "type": "pinned",
      "criteria": [
        {
          "type": "gte",
          "metadata": "loyalty_level",
          "values": [
            80
          ]
        }
      ],
      "actions": {
        "docs": [
          {
            "_index": "products",
            "_id": "6"
          }
        ]
      }
    }
  ]
}

Now, when running a query, we need to include the new parameter loyalty_level in the metadata. If the condition in the rule is met, the new document will appear on top of the results.

For example, when sending a query where the loyalty_level is 80:

POST /products/_search
{
  "retriever": {
    "rule": {
      "retriever": {
        "standard": {
          "query": {
            "match": {
              "name": "PlayStation"
            }
          }
        }
      },
      "match_criteria": {
        "query_string": "PlayStation",
        "loyalty_level": 80
      },
      "ruleset_ids": ["my-rules"]
    }
  }
}

We’ll see the loyalty document on top of the results:

{
  "took": 31,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 4,
      "relation": "eq"
    },
    "max_score": 1.7014122e+38,
    "hits": [
      {
        "_index": "products",
        "_id": "6",
        "_score": 1.7014122e+38,
        "_source": {
          "id": "6",
          "name": "PlayStation Plus Deluxe Card - 12 months",
          "category": "membership",
          "brand": "Sony",
          "price": 300
        }
      },
      {
        "_index": "products",
        "_id": "3",
        "_score": 0.5054567,
        "_source": {
          "id": "3",
          "name": "PlayStation 4 Camera",
          "category": "accessory",
          "brand": "Sony",
          "price": 200
        }
      },
      {
        "_index": "products",
        "_id": "1",
        "_score": 0.45618832,
        "_source": {
          "id": "1",
          "name": "PlayStation 4 Slim 1TB",
          "category": "console",
          "brand": "Sony",
          "price": 1200
        }
      },
      {
        "_index": "products",
        "_id": "4",
        "_score": 0.45618832,
        "_source": {
          "id": "4",
          "name": "PlayStation 4 VR Headset",
          "category": "accessory",
          "brand": "Sony",
          "price": 900
        }
      }
    ]
  }
}

In the case below, since the loyalty level is 70, the rule is not met, and the item should not appear on top:

POST /products/_search
{
  "retriever": {
    "rule": {
      "retriever": {
        "standard": {
          "query": {
            "match": {
              "name": "PlayStation"
            }
          }
        }
      },
      "match_criteria": {
        "query_string": "PlayStation",
        "loyalty_level": 70
      },
      "ruleset_ids": ["my-rules"]
    }
  }
}

Here are the results:

{
  "took": 7,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 4,
      "relation": "eq"
    },
    "max_score": 0.5054567,
    "hits": [
      {
        "_index": "products",
        "_id": "3",
        "_score": 0.5054567,
        "_source": {
          "id": "3",
          "name": "PlayStation 4 Camera",
          "category": "accessory",
          "brand": "Sony",
          "price": 200
        }
      },
      {
        "_index": "products",
        "_id": "1",
        "_score": 0.45618832,
        "_source": {
          "id": "1",
          "name": "PlayStation 4 Slim 1TB",
          "category": "console",
          "brand": "Sony",
          "price": 1200
        }
      },
      {
        "_index": "products",
        "_id": "4",
        "_score": 0.45618832,
        "_source": {
          "id": "4",
          "name": "PlayStation 4 VR Headset",
          "category": "accessory",
          "brand": "Sony",
          "price": 900
        }
      },
      {
        "_index": "products",
        "_id": "6",
        "_score": 0.3817649,
        "_source": {
          "id": "6",
          "name": "PlayStation Plus Deluxe Card - 12 months",
          "category": "membership",
          "brand": "Sony",
          "price": 300
        }
      }
    ]
  }
}

Example: immediate exclusion

Let’s pretend that our DualShock 4 Wireless Controller (ID 2) is temporarily unavailable and cannot be sold. So, instead of manually deleting the document or waiting for some data process to kick in, the business team decides to remove it from the search results in the meantime.

We’ll use a similar process to the one we’ve just applied to the popular items, but this time instead of selecting Pinned, we’ll choose Exclude. This rule works as a kind of blacklist. Change the criteria to Always so that the exclusion works every time the query runs.

The rule should look like this:

Save the rule and the ruleset to apply the changes. Here is the equivalent REST request:

PUT _query_rules/my-rules
{
  "rules": [
    {
      "rule_id": "rule-6358",
      "type": "pinned",
      "criteria": [
        {
          "type": "always"
        }
      ],
      "actions": {
        "docs": [
          {
            "_index": "products",
            "_id": "2"
          }
        ]
      }
    }
  ]
}

Now, when we run the query again, you’ll see that the item is no longer in the results, even though the prior rule is to pin it. This is because exclusions have priority over pinning results.

{
 "took": 6,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 4,
     "relation": "eq"
   },
   "max_score": 2.205655,
   "hits": [
     {
       "_index": "products",
       "_id": "3",
       "_score": 2.205655,
       "_source": {
         "id": "3",
         "name": "PlayStation 4 Camera",
         "category": "accessory",
         "brand": "Sony",
         "price": 200
       }
     },
     {
       "_index": "products",
       "_id": "1",
       "_score": 1.9738505,
       "_source": {
         "id": "1",
         "name": "PlayStation 4 Slim 1TB",
         "category": "console",
         "brand": "Sony",
         "price": 1200
       }
     },
     {
       "_index": "products",
       "_id": "4",
       "_score": 1.9738505,
       "_source": {
         "id": "4",
         "name": "PlayStation 4 VR Headset",
         "category": "accessory",
         "brand": "Sony",
         "price": 900
       }
     },
     {
       "_index": "products",
       "_id": "5",
       "_score": 0.69247496,
       "_source": {
         "id": "5",
         "name": "Charging Station for DualShock 4",
         "category": "accessory",
         "brand": "Sony",
         "price": 80
       }
     }
   ]
 }
}

Conclusion

Query Rules makes it very easy to adjust relevance without any code changes. The new Kibana UI allows you to make these changes in a matter of seconds, giving you and your business team more control over your search results.

Beyond e-commerce, Query Rules can power many other scenarios: highlighting troubleshooting guides in support portals, surfacing key internal documents in knowledge bases, promoting breaking stories in news sites, or filtering out expired job or content listings. They can even enforce compliance rules, like hiding restricted material by user role or region.

Ready to try this out on your own? Start a free trial.

Want to get Elastic certified? Find out when the next Elasticsearch Engineer training is running!

Related content

How to compare two Elasticsearch indices and find missing documents

April 6, 2026

How to compare two Elasticsearch indices and find missing documents

Exploring approaches for comparing two Elasticsearch indices and finding missing documents.

Testing Elasticsearch. It just got simpler.

March 19, 2026

Testing Elasticsearch. It just got simpler.

Explaining how Elasticsearch integration tests have become simpler thanks to improvements in Elasticsearch 9.x, the modern Java client, and Testcontainers 2.x.

Elasticsearch Serverless pricing demystified: VCUs and ECUs explained

December 19, 2025

Elasticsearch Serverless pricing demystified: VCUs and ECUs explained

Learn how Elasticsearch Serverless pricing works for Elastic’s fully-managed deployment offering. We explain VCUs (Search, Ingest, ML) and ECUs, detailing how consumption is based on actual allocated resources, workload complexity, and Search Power.

How excessive replica counts can degrade performance, and what to do about it

December 8, 2025

How excessive replica counts can degrade performance, and what to do about it

Learn about the impact of high replica counts in Elasticsearch, and how to ensure cluster stability by right-sizing your replicas.

How to deploy Elasticsearch on Azure AKS Automatic

November 14, 2025

How to deploy Elasticsearch on Azure AKS Automatic

Learn how to deploy Elasticsearch with Kibana on Azure using AKS Automatic and ECK for a partially managed Elasticsearch setup configuration.

Ready to build state of the art search experiences?

Sufficiently advanced search isn’t achieved with the efforts of one. Elasticsearch is powered by data scientists, ML ops, engineers, and many more who are just as passionate about search as your are. Let’s connect and work together to build the magical search experience that will get you the results you want.

Try it yourself