1. Overview

Elasticsearch is used as a search engine in Magento. Since Magento 2.4, Elasticsearch is a required component and installation of Magento without Elasticsearch is not possible.

To integrate Magento with Elasticsearch, the following steps are needed:

  • install proper version of Elasticsearch on the server
  • install proper version of PHP Elasticsearch library as a project dependency
  • specify the installed Elasticsearch version in Magento configuration (Catalog > Catalog Search > Search Engine)

The version of Elasticsearch should be compatible with the Elasticsearch library and Magento. Magento has a separate module for each Elasticsearch version. Not every version is supported.

By default, there is only one index in Elasticsearch for each Magento store – search index for products. All existing query types use the same index. There are no indexes for other entities such as categories, customers or CMS pages.

It is possible to create other indexes in Elasticsearch and new query types using these indexes, although it seems quite a large effort.

Magento ignores any indexes in Elasticsearch not defined in Magento, so it is possible to use the same instance of Elasticsearch for other purposes.

Elasticsearch is used mainly in storefront. Regular search in admin panel does not use Elasticsearch.

2. Reindex

Data in Elasticsearch is always secondary data, synchronized with data in MySQL database. The process of synchronization is called reindex. Magento provides different ways of making reindex:

  1. Full reindex – which can be triggered manually by a developer – in command line or in code
  2. Partial reindex – reindex of only those products that actually changed
    1. on save – reindex happens immediately when product is saved
    2. on schedule – saved product is only added to the queue of products waiting for reindex. A background process that runs every minute makes reindex of all products in queue asynchronously. This is a better option considering performance

The only Magento Indexer related to Elasticsearch is Catalog Search.

A product record in Elasticsearch looks like that:

{
   "store_id":"1",
   "sku":"24-MB01",
   "status":1,
   "status_value":"Enabled",
   "visibility":4,
   "name":"joust duffle bag",
   "url_key":"joust-duffle-bag",
   "description":"The sporty Joust Duffle Bag can't be beat - not in the gym, not on the luggage carousel, not anywhere. Big enough to haul a basketball or soccer ball and some sneakers with plenty of room to spare, it's ideal for athletes with places to go. Dual top handles. Adjustable shoulder strap. Full-length zipper. L 29\" x W 13\" x H 11\".",
   "category_ids":[
      2,
      3,
      4
   ],
   "position_category_2":"0",
   "name_category_2":"Default Category",
   "position_category_3":"0",
   "name_category_3":"Gear",
   "position_category_4":"0",
   "name_category_4":"Bags",
   "price_0_1":"34.000000",
   "price_1_1":"34.000000",
   "price_2_1":"34.000000",
   "price_3_1":"34.000000"
}

To index additional product data in Elasticsearch, the simplest solution is to create a product EAV attribute with proper parameters (e.g. Use n Search = Yes, or Visible in Advanced Search = Yes).

It is also possible to create a fake product attribute (static EAV attribute without actual column in database) and index an arbitrary value using a Magento plugin.

3. Search Request

Requests to Elasticsearch are built based on configuration in search_request.xml files.

The files define different query types. By default, Magento uses the following query types:

  • quick_search_container – Quick Search (the regular search at the top of the page)
  • advanced_search_container – Advanced Search
  • catalog_view_container – Category page with layered navigation
  • graphql_product_search_with_aggregation – used by GraphQL API
  • graphql_product_search – used by GraphQL API

In Magento Commerce B2B extension, there is one additional query type:

  • quick_order_suggestions_search_container used by Quick Order by SKU

Main search_request.xml file looks like that:

<?xml version="1.0"?>
<!--
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<requests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:noNamespaceSchemaLocation="urn:magento:framework:Search/etc/search_request.xsd">
    <request query="quick_search_container" index="catalogsearch_fulltext">
        <dimensions>
            <dimension name="scope" value="default"/>
        </dimensions>
        <queries>
            <query xsi:type="boolQuery" name="quick_search_container" boost="1">
                <queryReference clause="should" ref="search" />
                <queryReference clause="should" ref="partial_search" />
                <queryReference clause="must" ref="category"/>
                <queryReference clause="must" ref="price"/>
                <queryReference clause="must" ref="visibility"/>
            </query>
            <query xsi:type="matchQuery" value="$search_term$" name="search">
                <match field="*"/>
            </query>
            <query xsi:type="matchQuery" value="$search_term$" name="partial_search">
                <match field="*"/>
                <match field="name" matchCondition="match_phrase_prefix"/>
                <match field="sku" matchCondition="match_phrase_prefix"/>
            </query>
            <query xsi:type="filteredQuery" name="category">
                <filterReference clause="must" ref="category_filter"/>
            </query>
            <query xsi:type="filteredQuery" name="price">
                <filterReference clause="must" ref="price_filter"/>
            </query>
            <query xsi:type="filteredQuery" name="visibility">
                <filterReference clause="must" ref="visibility_filter"/>
            </query>
        </queries>
        <filters>
            <filter xsi:type="termFilter" name="category_filter" field="category_ids" value="$category_ids$"/>
            <filter xsi:type="rangeFilter" name="price_filter" field="price" from="$price.from$" to="$price.to$"/>
            <filter xsi:type="termFilter" name="visibility_filter" field="visibility" value="$visibility$"/>
        </filters>
        <aggregations>
            <bucket name="price_bucket" field="price" xsi:type="dynamicBucket" method="$price_dynamic_algorithm$">
                <metrics>
                    <metric type="count"/>
                </metrics>
            </bucket>
            <bucket name="category_bucket" field="category_ids" xsi:type="termBucket">
                <metrics>
                    <metric type="count"/>
                </metrics>
            </bucket>
        </aggregations>
        <from>0</from>
        <size>10000</size>
    </request>
    <request query="advanced_search_container" index="catalogsearch_fulltext">
        <dimensions>
            <dimension name="scope" value="default"/>
        </dimensions>
        <queries>
            <query xsi:type="boolQuery" name="advanced_search_container" boost="1">
                <queryReference clause="should" ref="sku_query"/>
                <queryReference clause="should" ref="price_query"/>
                <queryReference clause="should" ref="category_query"/>
                <queryReference clause="must" ref="visibility_query"/>
            </query>
            <query name="sku_query" xsi:type="filteredQuery">
                <filterReference clause="must" ref="sku_query_filter"/>
            </query>
            <query name="price_query" xsi:type="filteredQuery">
                <filterReference clause="must" ref="price_query_filter"/>
            </query>
            <query name="category_query" xsi:type="filteredQuery">
                <filterReference clause="must" ref="category_filter"/>
            </query>
            <query name="visibility_query" xsi:type="filteredQuery">
                <filterReference clause="must" ref="visibility_filter"/>
            </query>
        </queries>
        <filters>
            <filter xsi:type="wildcardFilter" name="sku_query_filter" field="sku" value="$sku$"/>
            <filter xsi:type="rangeFilter" name="price_query_filter" field="price" from="$price.from$" to="$price.to$"/>
            <filter xsi:type="termFilter" name="category_filter" field="category_ids" value="$category_ids$"/>
            <filter xsi:type="termFilter" name="visibility_filter" field="visibility" value="$visibility$"/>
        </filters>
        <from>0</from>
        <size>10000</size>
    </request>
    <request query="catalog_view_container" index="catalogsearch_fulltext">
        <dimensions>
            <dimension name="scope" value="default"/>
        </dimensions>
        <queries>
            <query xsi:type="boolQuery" name="catalog_view_container" boost="1">
                <queryReference clause="must" ref="category"/>
                <queryReference clause="must" ref="price"/>
                <queryReference clause="must" ref="visibility"/>
            </query>
            <query xsi:type="filteredQuery" name="category">
                <filterReference clause="must" ref="category_filter"/>
            </query>
            <query xsi:type="filteredQuery" name="price">
                <filterReference clause="must" ref="price_filter"/>
            </query>
            <query xsi:type="filteredQuery" name="visibility">
                <filterReference clause="must" ref="visibility_filter"/>
            </query>
        </queries>
        <filters>
            <filter xsi:type="termFilter" name="category_filter" field="category_ids" value="$category_ids$"/>
            <filter xsi:type="rangeFilter" name="price_filter" field="price" from="$price.from$" to="$price.to$"/>
            <filter xsi:type="termFilter" name="visibility_filter" field="visibility" value="$visibility$"/>
        </filters>
        <aggregations>
            <bucket name="price_bucket" field="price" xsi:type="dynamicBucket" method="$price_dynamic_algorithm$">
                <metrics>
                    <metric type="count"/>
                </metrics>
            </bucket>
            <bucket name="category_bucket" field="category_ids" xsi:type="termBucket">
                <metrics>
                    <metric type="count"/>
                </metrics>
            </bucket>
        </aggregations>
        <from>0</from>
        <size>10000</size>
    </request>
</requests>

The xml mentions some product fields like visibility. However, since Magento allows creating custom product attributes, even in the admin panel, query generated by this xml is further modified to include all searchable product attributes.

This is done by a plugin to xml reader:

Magento\CatalogSearch\Model\Search\ReaderPlugin::afterRead()
$result = array_merge_recursive(
  $xmlData,
  $this->requestGenerator->generate()
);

In RequestGenerator::generate(), Magento adds some dynamic changes to the search request, including clauses related to product attributes. It is possible to add additional changes to the request using a similar mechanism.

This plugin is executed once and cached. To see changes implemented in search_request.xml or added by plugins, it is necessary to clean cache (so this way, it is not possible to use different requests depending on customer, current date etc., although there may be other ways of implementing this).

Based on the xml and a list of searchable attributes, Magento sends a request to Elasticsearch, which may look as follows:

{
   "from":0,
   "size":12,
   "query":{
      "bool":{
         "must":[
            {
               "terms":{
                  "visibility":[
                     "3",
                     "4"
                  ],
                  "boost":1.0
               }
            }
         ],
         "should":[
            {
               "match":{
                  "_search":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":2.0
                  }
               }
            },
            {
               "match":{
                  "name":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":6.0
                  }
               }
            },
            {
               "match":{
                  "sku":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":7.0
                  }
               }
            },
            {
               "match":{
                  "description":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":2.0
                  }
               }
            },
            {
               "match":{
                  "short_description":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":2.0
                  }
               }
            },
            {
               "match":{
                  "manufacturer_value":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":2.0
                  }
               }
            },
            {
               "match":{
                  "status_value":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":2.0
                  }
               }
            },
            {
               "match":{
                  "url_key":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":2.0
                  }
               }
            },
            {
               "match":{
                  "tax_class_id_value":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":2.0
                  }
               }
            },
            {
               "match":{
                  "_search":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":2.0
                  }
               }
            },
            {
               "match_phrase_prefix":{
                  "name":{
                     "query":"my_search_term",
                     "slop":0,
                     "max_expansions":50,
                     "boost":2.0
                  }
               }
            },
            {
               "match_phrase_prefix":{
                  "sku":{
                     "query":"my_search_term",
                     "slop":0,
                     "max_expansions":50,
                     "boost":2.0
                  }
               }
            }
         ],
         "adjust_pure_negative":true,
         "minimum_should_match":"1",
         "boost":1.0
      }
   },
   "stored_fields":[
      "_id",
      "_score"
   ],
   "sort":[
      {
         "_score":{
            "order":"desc"
         }
      }
   ],
   "track_total_hits":2147483647,
   "aggregations":{
      "price_bucket":{
         "extended_stats":{
            "field":"price_0_1",
            "sigma":2.0
         }
      },
      "category_bucket":{
         "terms":{
            "field":"category_ids",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      }
   }
}

The format of search_request.xml allows the use of the following Elasticsearch queries:

  • boolQuery
  • matchQuery
  • filteredQuery

The number of search results is configured in search_request.xml, e.g. <size>10</size>. Default: 10 000. 10 000 is also a default limit in Elasticsearch (index.max_result_window). If it is necessary to return more than 10 000 results, changes in code and Elasticsearch configuration may be needed.

4. Category view page

Viewing the category page is basically a kind of search and the logic is very similar to the logic of quick search.

The basic mechanism is as follows:

  • Magento makes a request to Elasticsearch to find matching product ids
  • Based on the results from Elasticsearch, Magento loads the products from standard MySQL database (and may apply additional filters at this point)
  • Data from MySQL is displayed on the frontend.

Additionally, Magento uses Elasticsearch to get information about the number of matching products next to each option in layered navigation.

Example: customer enters category page Bags and selects some filters in layered navigation, e.g. Price $40.00-49.99

Category and filters are added to request url, e.g. http://mystore.pl/gear/bags.html?price=40-50

Magento parses the url parameters and makes request to Elasticsearch to get 2 pieces of information:

  • items – ids of products matching the new filters (only ids, no data)
  • aggregations – number of matching products for each option of each filter in layered navigation (after applying all new filters)

The request may look like that:

{
   "from":0,
   "size":12,
   "query":{
      "bool":{
         "must":[
            {
               "term":{
                  "category_ids":{
                     "value":"4",
                     "boost":1.0
                  }
               }
            },
            {
               "range":{
                  "price_0_1":{
                     "from":"40",
                     "to":"49.999",
                     "include_lower":true,
                     "include_upper":true,
                     "boost":1.0
                  }
               }
            },
            {
               "terms":{
                  "visibility":[
                     "2",
                     "4"
                  ],
                  "boost":1.0
               }
            }
         ],
         "adjust_pure_negative":true,
         "boost":1.0
      }
   },
   "stored_fields":[
      "_id",
      "_score"
   ],
   "sort":[
      {
         "position_category_4":{
            "order":"asc"
         }
      }
   ],
   "track_total_hits":2147483647,
   "aggregations":{
      "price_bucket":{
         "extended_stats":{
            "field":"price_0_1",
            "sigma":2.0
         }
      },
      "category_bucket":{
         "terms":{
            "field":"category_ids",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "manufacturer_bucket":{
         "terms":{
            "field":"manufacturer",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "color_bucket":{
         "terms":{
            "field":"color",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "activity_bucket":{
         "terms":{
            "field":"activity",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "style_bags_bucket":{
         "terms":{
            "field":"style_bags",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "material_bucket":{
         "terms":{
            "field":"material",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "strap_bags_bucket":{
         "terms":{
            "field":"strap_bags",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "features_bags_bucket":{
         "terms":{
            "field":"features_bags",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "gender_bucket":{
         "terms":{
            "field":"gender",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "category_gear_bucket":{
         "terms":{
            "field":"category_gear",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "size_bucket":{
         "terms":{
            "field":"size",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "eco_collection_bucket":{
         "terms":{
            "field":"eco_collection",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "performance_fabric_bucket":{
         "terms":{
            "field":"performance_fabric",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "erin_recommends_bucket":{
         "terms":{
            "field":"erin_recommends",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "new_bucket":{
         "terms":{
            "field":"new",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "sale_bucket":{
         "terms":{
            "field":"sale",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "format_bucket":{
         "terms":{
            "field":"format",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "purpose_bucket":{
         "terms":{
            "field":"purpose",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "style_bottom_bucket":{
         "terms":{
            "field":"style_bottom",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "style_general_bucket":{
         "terms":{
            "field":"style_general",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "sleeve_bucket":{
         "terms":{
            "field":"sleeve",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "collar_bucket":{
         "terms":{
            "field":"collar",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "pattern_bucket":{
         "terms":{
            "field":"pattern",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      },
      "climate_bucket":{
         "terms":{
            "field":"climate",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      }
   }
}

After receiving a list of products from Elasticsearch, Magento fetches the products from MySQL database. The SQL includes filter by ids from Elasticsearch + some additional filters. It may look like that:

SELECT `e`.*, 
       `price_index`.`price`, 
       `price_index`.`tax_class_id`, 
       `price_index`.`final_price`, 
       IF(price_index.tier_price IS NOT NULL, Least(price_index.min_price, 
                                              price_index.tier_price), 
       price_index.min_price)                   AS `minimal_price`, 
       `price_index`.`min_price`, 
       `price_index`.`max_price`, 
       `price_index`.`tier_price`, 
       Ifnull(review_summary.reviews_count, 0)  AS `reviews_count`, 
       Ifnull(review_summary.rating_summary, 0) AS `rating_summary`, 
       `stock_status_index`.`stock_status`      AS `is_salable` 
FROM   `catalog_product_entity` AS `e` 
       INNER JOIN `catalog_product_index_price` AS `price_index` 
               ON price_index.entity_id = e.entity_id 
                  AND price_index.customer_group_id = 0 
                  AND price_index.website_id = '1' 
       LEFT JOIN `review_entity_summary` AS `review_summary` 
              ON e.entity_id = review_summary.entity_pk_value 
                 AND review_summary.store_id = 1 
                 AND review_summary.entity_type = (SELECT 
                     `review_entity`.`entity_id` 
                                                   FROM   `review_entity` 
                                                   WHERE  ( entity_code = 
                                                            'product' )) 
       INNER JOIN `cataloginventory_stock_status` AS `stock_status_index` 
               ON e.entity_id = stock_status_index.product_id 
WHERE  ( ( stock_status_index.stock_status = 1 ) 
         AND ( e.entity_id IN ( 4, 5, 13, 14 ) ) ) 
       AND ( e.created_in <= 1 ) 
       AND ( e.updated_in > 1 ) 
ORDER  BY Field(e.entity_id, 4, 5, 13, 14);

In this SQL, the following part is based on the results from Elasticsearch:

e.entity_id IN ( 4, 5, 13, 14 ).

This is actually filter by category and price.

The logic of filtering based on choices in layered navigation is applied to Elasticsearch request only. The logic of filtering based on visibility (product visibility, category permissions etc.) is applied twice: first as part of Elasticsearch request, then again as part of the SQL. Therefore, it might happen that product that should be visible is not visible due to invalid data in Elasticsearch, but it should not happen that product that should be invisible is visible due to invalid data in Elasticsearch.

Custom code that applies additional visibility filters should also apply the filters twice. If the filter is applied to MySQL only, it is possible that the displayed number of products found will be higher than the actual number of products on the page.

After loading products, Magento builds the view of layered navigation. Each filter is a select with options. For each option, Magento presents the number of matching products. This number comes from Elasticsearch (aggregations).

In previous versions of Magento, there was a choice to use MySQL instead of Elasticsearch for fulltext search. At that time, MySQL EAV indexer was used to support layered navigation. According to Magento documentation, if the merchant uses Elasticsearch for fulltext search and there is no custom extension depending on EAV indexer, EAV indexer is not needed and can be disabled in the configuration:  Stores > Settings > Configuration > Catalog > Catalog > Catalog Search > Enable EAV Indexer.

Layered navigation can be hidden by a change in the layout XML file. However, the underlying logic of layered navigation cannot be disabled without considerable effort.

Product attributes are available in layered navigation based on their parameters

  • Use in Layered Navigation (code “is_filterable”) – layered navigation on the category page
  • Use in Search Results Layered Navigation (code “is_filterable_in_search”) – layered navigation on search results page

Only some data types and input types can be used in layered navigation. E.g. text attributes cannot be used in layered navigation.

If there is some mismatch between Elasticsearch and MySQL, the common indicators are:

  • products that should be visible are not visible on a list, but can be accessed directly using product page url
  • the count of products displayed in some place of the page does not match the actual number of products visible

Category page is cached by full page cache, so the search actually takes place only once, the first time any customer enters the page. Subsequent customers from the same group receive page from cache, so no request to Elasticsearch or MySQL is made. Flushing full page cache is necessary to refresh search results.

Each category has a parameter “Is Anchor”: Yes or No.

If there is a category structure like that:

Juices

  Orange juices

If Juices has the parameter “Is Anchor” = Yes, Juices will also show Orange Juices. If Juices has the parameter “Is Anchor” = No, Juices will only display products directly assigned to category Juices.

5. Quick Search

The basic mechanism is as follows:

  • Magento makes a request to Elasticsearch to find matching product ids
  • Based on the results from Elasticsearch, Magento loads the products from standard MySQL database (and may apply additional filters at this point)
  • Data from MySQL is displayed on the frontend.

Data kept in Elasticsearch is never really displayed on the frontend. It is only used for searching, but once the product is found, it is loaded from the MySQL database and all data comes from MySQL.

Example: if product name is Cat in MySQL and Dog in Elasticsearch:

  • When you type “Cat”, you get 0 results
  • When you type “Dog”, you can see a product named “Cat” in the results

If product is not indexed in Elasticsearch, it cannot be found.

Attributes used for search include some basic attributes + EAV attributes with proper properties, e.g. Use in Search – Yes

Request to Elasticsearch may look like that:

{
   "from":0,
   "size":12,
   "query":{
      "bool":{
         "must":[
            {
               "terms":{
                  "visibility":[
                     "3",
                     "4"
                  ],
                  "boost":1.0
               }
            }
         ],
         "should":[
            {
               "match":{
                  "_search":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":2.0
                  }
               }
            },
            {
               "match":{
                  "name":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":6.0
                  }
               }
            },
            {
               "match":{
                  "sku":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":7.0
                  }
               }
            },
            {
               "match":{
                  "description":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":2.0
                  }
               }
            },
            {
               "match":{
                  "short_description":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":2.0
                  }
               }
            },
            {
               "match":{
                  "manufacturer_value":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":2.0
                  }
               }
            },
            {
               "match":{
                  "status_value":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":2.0
                  }
               }
            },
            {
               "match":{
                  "url_key":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":2.0
                  }
               }
            },
            {
               "match":{
                  "tax_class_id_value":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":2.0
                  }
               }
            },
            {
               "match":{
                  "_search":{
                     "query":"my_search_term",
                     "operator":"OR",
                     "prefix_length":0,
                     "max_expansions":50,
                     "fuzzy_transpositions":true,
                     "lenient":false,
                     "zero_terms_query":"NONE",
                     "auto_generate_synonyms_phrase_query":true,
                     "boost":2.0
                  }
               }
            },
            {
               "match_phrase_prefix":{
                  "name":{
                     "query":"my_search_term",
                     "slop":0,
                     "max_expansions":50,
                     "boost":2.0
                  }
               }
            },
            {
               "match_phrase_prefix":{
                  "sku":{
                     "query":"my_search_term",
                     "slop":0,
                     "max_expansions":50,
                     "boost":2.0
                  }
               }
            }
         ],
         "adjust_pure_negative":true,
         "minimum_should_match":"1",
         "boost":1.0
      }
   },
   "stored_fields":[
      "_id",
      "_score"
   ],
   "sort":[
      {
         "_score":{
            "order":"desc"
         }
      }
   ],
   "track_total_hits":2147483647,
   "aggregations":{
      "price_bucket":{
         "extended_stats":{
            "field":"price_0_1",
            "sigma":2.0
         }
      },
      "category_bucket":{
         "terms":{
            "field":"category_ids",
            "size":500,
            "min_doc_count":1,
            "shard_min_doc_count":0,
            "show_term_doc_count_error":false,
            "order":[
               {
                  "_count":"desc"
               },
               {
                  "_key":"asc"
               }
            ]
         }
      }
   }
}

By default, Magento uses OR condition, so if you type “sku1 sku2”, you may find two products. If you type “orange juice”, you may find orange t-shirt and raspberry juice.

A search request sent to Elasticsearch includes many clauses, e.g. the name should be “Cat”, the description should be “Cat”, a short description should be “Cat”. It is enough for one clause to be true to find a  product. This depends on Elasticsearch parameter  minimum_should_match, which has a value of 1 hard-coded in Magento.

If you make a typo in a search term, Magento does not show the product in search results, but there may be a search suggestion (“Did you mean …?”) with a given product. If you type just the middle part of a word – the same effect as for typos. Search suggestions are a separate feature of Magento and they are provided by a separate request to Elasticsearch.

In Magento Commerce, there is a configuration for which customer groups can use search bar (Category Permissions > Disallow Catalog Search By).

The search results page has layered navigation that allows to further filter the results. There is a separate list of attributes that can be used in layered navigation in the category page vs. search result page.

6. Advanced Search

Advanced search is similar to quick search, but the customer can configure search details.

Attributes are displayed in advanced search form based on parameter: Visible in Advanced Search.

7. GraphQL

GraphQL is one of Magento APIs. It allows external systems to fetch data from Magento. GraphQL allows to get a list of products. There is an endpoint that allows to get a list of products based on fulltext search, which uses pretty much the same logic as search on storefront.

8. Search suggestions

Search Suggestions is an additional feature of Magento Commerce, it displays a block “Did you mean?” on the search results page.

Suggestions are loaded from Elasticsearch by a separate query that may look as follows:

{
   "suggest":{
      "text":"my_search_term",
      "phrase_style_general_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"style_general_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"style_general_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_style_bags_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"style_bags_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"style_bags_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_strap_bags_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"strap_bags_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"strap_bags_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_eco_collection_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"eco_collection_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"eco_collection_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_color_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"color_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"color_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_features_bags_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"features_bags_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"features_bags_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_allow_open_amount_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"allow_open_amount_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"allow_open_amount_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_country_of_manufacture_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"country_of_manufacture_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"country_of_manufacture_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_manufacturer_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"manufacturer_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"manufacturer_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_format_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"format_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"format_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_giftcard_type_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"giftcard_type_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"giftcard_type_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_climate_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"climate_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"climate_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_collar_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"collar_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"collar_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_description":{
         "phrase":{
            "analyzer":"standard",
            "field":"description",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"description",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_sale_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"sale_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"sale_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_price_view_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"price_view_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"price_view_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_custom_layout_update_file_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"custom_layout_update_file_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"custom_layout_update_file_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_is_returnable_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"is_returnable_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"is_returnable_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_page_layout_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"page_layout_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"page_layout_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_gift_wrapping_available_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"gift_wrapping_available_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"gift_wrapping_available_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_shipment_type_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"shipment_type_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"shipment_type_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_msrp_display_actual_price_type_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"msrp_display_actual_price_type_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"msrp_display_actual_price_type_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_purpose_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"purpose_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"purpose_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_size_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"size_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"size_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_status_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"status_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"status_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_custom_design_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"custom_design_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"custom_design_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_options_container_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"options_container_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"options_container_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_style_bottom_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"style_bottom_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"style_bottom_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_tax_class_id_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"tax_class_id_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"tax_class_id_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_short_description":{
         "phrase":{
            "analyzer":"standard",
            "field":"short_description",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"short_description",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_activity_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"activity_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"activity_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_custom_layout_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"custom_layout_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"custom_layout_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_quantity_and_stock_status_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"quantity_and_stock_status_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"quantity_and_stock_status_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_name":{
         "phrase":{
            "analyzer":"standard",
            "field":"name",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"name",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_category_gear_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"category_gear_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"category_gear_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_gender_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"gender_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"gender_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_sleeve_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"sleeve_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"sleeve_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_performance_fabric_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"performance_fabric_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"performance_fabric_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_material_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"material_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"material_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_pattern_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"pattern_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"pattern_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_erin_recommends_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"erin_recommends_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"erin_recommends_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_url_key":{
         "phrase":{
            "analyzer":"standard",
            "field":"url_key",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"url_key",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_visibility_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"visibility_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"visibility_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_sku":{
         "phrase":{
            "analyzer":"standard",
            "field":"sku",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"sku",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_gift_message_available_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"gift_message_available_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"gift_message_available_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      },
      "phrase_new_value":{
         "phrase":{
            "analyzer":"standard",
            "field":"new_value",
            "size":2,
            "real_word_error_likelihood":0.95,
            "confidence":1.0,
            "separator":" ",
            "max_errors":1.0,
            "force_unigrams":true,
            "token_limit":10,
            "direct_generator":[
               {
                  "field":"new_value",
                  "min_word_length":3,
                  "min_doc_freq":1.0
               }
            ]
         }
      }
   }
}

There is a hardcoded limit of max_errors = 1, so the feature will not provide relevant results when customer makes more than one typo.

Elasticsearch returns a list of suggestions with a score for each attribute separately (sku, name, description, …).

Suggestions are displayed even if there are correct results.

9. Additional search features

It is possible to see popular search terms in admin panel: Marketing > SEO & Search > Search Terms

This feature uses MySQL database only.

It is also possible to define a redirect for a search term, e.g. when a customer types “Juices”, he/she may be redirected to the category page of Juices (or any other page). However, there needs to be an exact match. This feature does not use Elasticsearch.

It is also possible to specify synonyms for search:

There is a feature of Search Recommendations (“Related search terms”), which is similar but different from search suggestions.

10. Vue Storefront

Vue Storefront is a frontend PWA application which can be connected to Magento. Vue Storefront uses Elasticsearch as NoSQL database. Entities from Magento are indexed in Elasticsearch, so that Vue Storefront can access them directly without requests to Magento.

Indexing of data from Magento is done by custom application mage2vuestorefront (https://github.com/vuestorefront/mage2vuestorefront) written in Javascript that fetches data from Magento using Magento Rest API. It supports many entities such as categories, product reviews, CMS pages.


Elasticsearch has a lot of strengths and is the most powerful search engine, available for free. It analyzes a high volume of data in almost real-time, providing users with needed results immediately. It provides a lot of features that are worth to be discovered and applied to your eCommerce store.
If you need any help with your store and would like to discuss how Elasticsearch can be used in your business, contact us and we will be pleased to help you.