Concepts / Managing results / Implementing query rules
Aug. 08, 2019

Implementing Query Rules

Using the Query Rules Dashboard

We suggest you start using Query Rules with the Dashboard. The dashboard provides you with an easy interface to create and configure your rules. And importantly, you can easily test your choices live using the Dashboard’s search interface.

In fact, you might never need to go beyond the Dashboard - especially if your rules are few or they follow a very clearly defined pattern.

In this way, you can consider the Dashboard as a way to manage static rules - rules that remain in place for some time, or whose effect is global and constant.

For the Dashboard, you’ll need to go to the Query Rules tab and click on “Add Query Rule”.

Coding Query Rules with the API Client

Coding the rules using the Query Rules API becomes necessary when you need more programmatic control. This move from static to more dynamic rules can arise for a number of reasons - like when the rules become too numerous to manage one-by-one, or when you want to start building (and deleting) rules based on the content of your database. You’ll need to use the API if your rules change with every new update of your index, or if you provide your own frontend to manage your rules.

For the API, you’ll need to set up your index properly, and then set up the query rules at indexing time.

How Query Rules works

Rules are essentially If-Then configurations, or condition/consequence pairs.

  • The if involves parsing the user-entered query and seeing if any part of the query satisfies a condition (that we’ve set up with Query Rules).

  • If the if condition is satisfied, we process the then consequence.

Generally stated: The search engine adapts its behavior according to the consequences associated with a condition. If the text contains “red”, this is the condition that has the consequence that the engine filter on the facet “color”.

And that’s only one kind of consequence. As described in our overview, and presented in technical detail below - there are many kinds of consequences that adapt the search and alter the relevance.

Query Rules are processed at indexing time. This prepares the index, so that when a search is performed, the data is already prepared for the If-Then matching.

Keep in mind:

  • For the rule to be triggered, the condition needs to match the entire word (fully and exactly)
  • All text matching is case insensitive
  • Conditions can match on multiple phrases, but again, the multiple phrases have to match exactly, and in the correct order ( “a b” does not match “b a”)).

Query Rules comes with no noticeable impact on performance. Most of the work is done during indexing, so the search performance is not affected.

Enabling & disabling rules

You may disable a rule by setting its enabled flag to false. Disabled rules remain in the index, meaning they are still searchable, but are ignored at query time. This is a handy alternative to deleting a rule when you wish to disable it only temporarily.

For even more fine-grained control, you may specify validity windows on rules (via the validity attribute). Rules with validity windows only apply during the specified time frames; they are ignored the rest of the time. Rules with no time windows always apply. Like with the enabled flag, rules with validity windows remain searchable all the time.

API Overview

Pre-processing / Post-processing

As suggested, Query Rules allows fine-tuning results for queries matching specific patterns. They do so in two complementary ways:

  1. Query pre-processing. Rules may alter the query parameters - not only the query string, but also filters, facets, etc. - before the query is processed.

  2. Results post-processing. Rules may cause results (hits) to be ranked differently for specific queries. They may also add user data to the results.

Rules are complementary to the traditional ranking and textual relevance settings: while settings act globally on every query to an index, rules act selectively on specific queries.

Condition / Consequence

The general syntax is as follows:

1
2
3
4
{
    "condition": { /* What the query must match for the rule to be applied.  */ },
    "consequence": { /* How the query will be modified if the rule is applied. */ }
}

Condition

A rule’s condition identifies query strings matching a specific pattern.

More precisely, it is composed of:

  • A mandatory query pattern (acting on the full text query string, i.e. the query search parameter).

  • An optional context, which must match those supplied at query time (via the ruleContexts parameter).

A rule with a context is said to be contextual; a rule without a context is general.

Consequence

A rule’s consequence can be one or more actions.

  • Add query parameters
  • Automatic facet filter
  • Automatic optional facet filter
  • Remove or replace a word from the query string
  • Replace the query string entirely
  • Promote or hide specific hits
  • Return user data.

Condition - adapting the query

Query pattern

The query pattern is the most important piece of the rule’s condition. It consists of a sequence of tokens, treated as a phrase (i.e. all tokens must appear contiguously and in the specified order).

The allowed token types are:

  • Literal: a plain word that must appear as is. Matching is case insensitive, but not typo tolerant. Plurals and synonyms are not taken into account.
  • Facet value placeholder: will match any value of a given facet in the same index. The facet must have been declared in attributesForFaceting. Matching is case insensitive. Contrary to literals, facet values may be phrases.

It is worth noting that the pattern is implicitly a phrase, i.e. the order of words matters: foo bar and bar foo are not identical patterns.

In addition, the pattern has an anchoring type, depending on whether its boundaries (beginning, end) must coincide with the boundaries of the query string:

  • is: the pattern must exactly match the entire query string;
  • starts with: the pattern must match the beginning of the query string, but there may be extra words at the end;
  • ends with: the pattern must match the end of the query string, but there may be extra words at the beginning;
  • contains: the pattern can match any part of the query string.

Escape characters within a pattern

If you want to declare a query rule with a pattern that contains a colon (‘:’), or any special character, you will need to escape the character: "pattern": "thrones\\:episode". (The first ‘' is for JSON and the second is for the Query Rules engine to handle the ‘:’ properly.)

Context

To narrow and customize the scope of Query Rules, you can use the ruleContexts parameter. This is a string that you can assign to your Query Rules. You can also pass contexts as parameters during your search: doing so activates all Query Rules that share the context you passed (note, this does not mean that the events associated with the activated query rules are triggered, but that they can be triggered). For example, you can define a set of query rules with the context “homepage” and pass the same context value along with any search that originates from your homepage. This results in a set of query rules that apply to the homepage search and gives a more customized search experience.

A rule can have one context at most, but the query may specify multiple contexts which are treated as disjunctive (OR). When one or more contexts are specified at query time, contextual rules matching any of those contexts are activated. Note that general rules are always activated, no matter whether contexts are specified or not.

The context’s primary goal is to conditionally enable rules for only a subset of queries (e.g. in a specific category of an e-commerce application).

If present, the context is a string that must be passed at query time in the ruleContexts search parameter with the exact same value for the rule to be triggered. Matching is case sensitive.

The ruleContexts parameter is an array of contexts, which allows you to enable multiple contexts at the same time

A rule context must consist only of alphanumeric characters, hyphens, and underscores.

Consequence - adapting the results / relevance

A rule’s consequence can be one or more actions.

Add query parameters

Any number of any valid search parameters are supported. Note that these parameters are literals, i.e. constants.

What this consequence allows you to do is add or change any of the search settings that have the option to be changed at query time (the time when the query is sent to Algolia). Note that some settings that affect search can only be set at indexing time, meaning they cannot be changed “on the fly” at query time, like Searchable Attributes.

Within the list of all our search parameters, anytime the parameter has search as part of its scope, it can be changed on the fly at query time.

One common example of a search parameter that is useful to change at query time is the filters parameter. Let’s say when someone searches “tablet”, you want to show not only tablets (e.g. products with ‘tablets’ in their categories attribute) but also laptops that can be used as tablets (e.g. products with ‘hybrids’ in their categories attribute). To do this in the dashboard, click Add Query Parameter when adding a consequence for your Query Rule, and add the following JSON into the editor:

1
2
3
{
  "filters": "categories:tablets OR categories:hybrids"
}

You can also add filters with the API.

Automatic facet filter

Automatically transform a word or group of words into a facet filter if it matches a value in a given facet. User enters “red”, which is also a value in the color facet. A rule can be set up to transform red to behave like a filter.

Another approach would be to use the filters parameter (using the “add query parameters” rule). But with this approach, you will need to have 1 rule per filter value. So if you have 10 color values in your color facet, you will need to create 10 rules, 1 for each color.

It is more powerful to use automatic faceting: instead of creating 1 rule for each color, you create 1 rule per facet. Whether the user types “blue” or “red”, this single rule will apply. It also applies to new colors added to the facet.

Filters can be either conjunctive (AND, default) or disjunctive (OR). This only applies when the pattern matches multiple values of the same facet. In that situation, the rule matches multiple times, hence producing multiple filters. By default, the produced filters are combined with an AND operator. If you specify the filter to be disjunctive, however, an OR operator is used instead. Note that, in both cases, relationships to other filters in the query will always be conjunctive (AND).

Optionally, a score can be specified for the created filter. This is useful mainly for optional facet filters (see below).

For example, to automatically filter on the brand facet, using OR in case of multiple occurrences:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
    /* [...] */
    "condition": {
        "pattern": "{facet:brand}"
        /* [...] */
    },
    "consequence": {
        "params": {
            "automaticFacetFilters": [
                {
                    "facet": "brand",
                    "disjunctive": "true"
                }
            ]
        }
    }
}

Automatic optional facet filter

Same as automaticFacetFilters, but the filters are optional. Behaves like optionalFacetFilters.

Remove or replace a word from the query string

Removing or replacing words is achieved by specifying a list of edits to be applied to the query string. An edit can either:

  • Remove a word (or facet placeholder)
  • Replace a word (or facet placeholder) with another word

For example, the rule below specifies that when matching foo bar, the word foo should be removed and the word bar should be replaced with baz:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
    /* [...] */
    "condition": {
        "pattern": "foo bar"
        /* [...] */
    },
    "consequence": {
        "params": {
            "query": {
                "edits": [
                    {
                        "type": "remove",
                        "delete": "foo"
                    },
                    {
                        "type": "replace",
                        "delete": "bar",
                        "insert": "baz"
                    }
                ]
            }
        }
    }
}

Words that are removed or replaced from the query string are still highlighted or snippeted in the query’s results, so it’s not misleading for the end user.

Replace the query string entirely

To replace the query string entirely, specify a new string for the query parameter, as you would do for any other search parameter:

1
2
3
4
5
6
7
8
9
10
11
12
{
    /* [...] */
    "condition": {
        "pattern": "original query that will be replaced"
        /* [...] */
    },
    "consequence": {
        "params": {
            "query": "new text to be searched"
        }
    }
}

This and removing a word are mutually exclusive rules. It may also have an impact on subsequent rules (see Matching algorithm).

Promote or hide specific hits

One or more objects from the same index, identified by their objectID, are promoted to specific positions in the hits, or removed from them. This works seamlessly with Algolia’s built-in pagination, mingling promoted hits with regular hits and removing hidden hits while keeping pages the same size.

Hit promotion is detailed below.

Return user data.

This user data is returned outside of the hits, in a dedicated userData array. If multiple rules are applied, each user data is appended to this array. User data can be used to display specific information that does not affect pagination.

Rule matching algorithm

Sometimes, Query Rules can contradict or override each other. There’s some logic to the way Algolia handles it.

Conflicting rules

Your index may contain many rules, so you need to be careful not to repeat or create rules that conflict. Most rules target different and distinct situations, so this is usually not a concern. However, consider the following two rules:

  • Query Rule 1: if a query contains “potter”, then promote “Harry Potter’s Deluxe DVD collection” to the first result.
  • Query Rule 2: if a query contains “potter”, then promote the “Harry Potter 8” to the first result.

What is the first result - “Harry Potter DVD collection” or “Harry Potter 8”? In other words, which rule wins?

The Query Rule engine resolves this kind of conflict with a preset precedence logic, as outlined below. It resolves every conflict depending on the below set of criteria.

Precedence acts mainly along two axes: specificity (the more specific a rule is, the higher precedence it has — similar to CSS selectors) and query text.

Note that multiple rules can still match a given query, provided they match a distinct set of words.

Precedence logic

Algolia uses a tie-breaking algorithm, much like the ranking formula, to determine the precedence of all the Query Rules that apply to any given query. In other words, a criterion is only considered when all its preceding criteria rank equal.

The precedence logic, ranked by importance, is as follows:

  • Position: The earliest match wins (i.e. closest to the beginning of the query string).
  • Match length: The longest match wins (in terms of the number of words from the query string).
  • Anchoring: is > starts with > ends with > contains.
  • Context: A contextual rule has higher priority than a general rule.
  • Literals over placeholders: If a word could match both a literal or a facet value, the literal takes precedence.
  • Temporary over permanent: If both a temporary rule and a permanent rule match, the temporary rule takes precedence.
  • Rule ID: If there are still conflicts after all other criteria have been applied, we just take the smallest objectID in lexicographical order. This final tie-breaker rule is guaranteed to break every tie and it will most likely only be used when there are duplicate rules.

Essentially, rules are applied from left to right (or more precisely, from the beginning of the query string to its end).

Conflicting consequences

We only make use of the precedence logic above if two or more conditions conflict. If the conditions do not directly conflict, then there is no conflict, and so the precedence does not apply.

So what happens when two consequences conflict, or overlap? For example:

  • Query Rule 1: if a query contains “Shakespeare”, then promote “The Lost Shakespeare Diaries” to the first result.
  • Query Rule 2: if a query contains the phrase “how much is”, then promote your company’s “Full Price List” to the first result.

What happens when the query is “how much is Shakespeare”?

In this case, there is no conflict because the conditions are not the same. Yet, the consequences overlap: namely, two different rules/conditions are fighting for the first position in the results. In this case, there is no clear precedence logic to determine the winner. The result can sometimes be based on the creation date of the rules. The best is to avoid this kind of situation.

Edge cases

  • If a rule removes a word from the query string, all subsequent rules that would have been triggered by this word (be it via a literal or a facet placeholder) are disabled.
  • If a rule replaces the query string entirely, all subsequent rules are disabled.

Some further considerations

Hit promotion’s effect on relevance

Only objects coming from the same index can be promoted. Promoted objects have to be explicitly identified by their objectID.

A promoted object will always be considered a hit, even if it doesn’t match the query. If it would have matched the query, it is removed from its original position and inserted at its promoted position, even if the original position would have been better than the promoted position (in other words, promoted hits can also be “demoted”). For performance reasons, promoted positions are restricted to the range [0, 50] (keep in mind that positions are zero-based).

Inside the same rule, each promoted object must have a different promoted position. If promoted objects from two distinct rules are triggered for the same query:

  • Any duplicates are merged, using the best position.
  • If the resulting positions conflict between distinct objects, objects are shifted down until a free slot is found.
  • All regular hits are shifted down as many times as necessary to ensure that all promoted objects get as close to their promoted position as possible (modulo conflicts between objects, as stated above).

Hidden objects are removed from the hits. The following hits are shifted up so that pagination works seamlessly.

Injecting user data

User data allows to inject data inside the results that are not objects coming from the index, and as such doesn’t compete with other hits for pagination. A typical use-case would be to display a banner on top of the result list.

User data can be any JSON object. It is not interpreted by the API whatsoever.

Did you find this page helpful?