Understanding Magento CatalogSearch Architecture in Detail
AI generated
_doc
_index
Magento · CatalogSearch · Elasticsearch · Architecture
Magento CatalogSearch Architecture in Detail
from product attribute to Elasticsearch document

The Magento CatalogSearch architecture looks like a black box from the outside: you update an attribute, wait for the reindex, and hope search returns the right result. Understanding how the indexer, fulltext scope tables and the Elasticsearch adapter work together lets you debug, extend and fix performance issues at the right place instead of chasing symptoms.

19 min read Indexer · FieldMapper · Fulltext Scope · Search Request Magento 2.4.x · Elasticsearch 8.x · OpenSearch 2.x

1. CatalogSearch as architecture, not a feature

Magento CatalogSearch is the umbrella term for all components that make product search in Magento possible: indexers, database staging tables, field mapping classes, query builders, and the actual Elasticsearch or OpenSearch cluster. Anyone who treats CatalogSearch as just a configuration toggle under Stores > Configuration > Catalog > Catalog Search misses that a multi-stage pipeline sits behind that toggle, triggered on every attribute update, every price change and every new product. Understanding that pipeline is the difference between trial-and-error debugging and targeted intervention at the right place.

The CatalogSearch architecture in Magento deliberately separates two responsibilities. On one side sits indexing, which transforms product data from the EAV model into a searchable format and sends it to the search cluster. On the other side sits the search request itself, which builds a structured Elasticsearch request from a user query string and translates the results back into Magento objects. Both sides are decoupled through interfaces, so Magento could in theory work with any search module implementing the same contracts. In practice, Elasticsearch respectively OpenSearch has been the only supported production engine since Magento 2.4.

This article follows the CatalogSearch architecture along its natural data flow: from the involved modules through the indexer pipeline and the fulltext scope table to the finished Elasticsearch document, and then back from the search form through the query builder to the rendered result. Once you have traced this path completely, you can extend any point of it deliberately, without reinventing the entire pipeline.

2. The Elasticsearch modules at a glance

Magento does not ship its Elasticsearch integration as a single module but as a layer of several modules building on each other. Magento_Elasticsearch contains the generic, engine-agnostic base classes: interfaces for FieldMapper, query builder and adapter. Version-specific modules such as Magento_Elasticsearch7 and Magento_OpenSearch build on this and encapsulate the concrete client libraries plus version-specific quirks of the respective search engine. Which engine is actually active is determined by the catalog/search/engine configuration, typically set to elasticsearch7 or opensearch.

This module separation in the CatalogSearch architecture has a practical reason: Elasticsearch and OpenSearch differ in request syntax details, supported aggregation types, and versioning of client libraries. Instead of scattering these differences across the code with version checks, Magento encapsulates them in swappable modules wired up through di.xml preferences and virtual types. Switching the engine ideally only requires a configuration change plus a full reindex, not a code rewrite.

For custom extensions of the CatalogSearch architecture it matters that most extension points live in Magento_Elasticsearch and therefore work regardless of the engine. FieldMapper, BatchDataMapper and most query builder classes are kept generic. Only when engine-specific behavior is genuinely needed, for example an OpenSearch-specific analyzer, do you need to work in the version-specific modules.


<!-- app/code/Mironsoft/SearchExtension/etc/di.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">

    <!-- Register a custom field mapper that decorates the default one -->
    <type name="Magento\Elasticsearch\Model\Adapter\FieldMapperInterface">
        <plugin name="mironsoft_custom_field_mapper"
                type="Mironsoft\SearchExtension\Plugin\FieldMapperPlugin"
                sortOrder="10"/>
    </type>

    <!-- Preference example: swap the default batch size provider -->
    <preference for="Magento\Elasticsearch\Model\Config"
                type="Mironsoft\SearchExtension\Model\Config"/>
</config>

3. The indexer pipeline: from EAV attribute to document

The central indexer of the CatalogSearch architecture carries the code catalogsearch_fulltext and is triggered by the Magento indexer framework both on attribute updates and on price or stock changes. It runs either synchronously on "Update on Save" or asynchronously via the message queue on "Update by Schedule," the setting typically used in production environments. In both cases the same core logic runs: an action object collects all relevant attribute values per store view for a set of product IDs, builds structured records from them, and passes them to the search engine layer.

The first step of this pipeline is purely database-side: Magento reads EAV values across several attribute tables (catalog_product_entity_varchar, _int, _decimal, _text, _datetime) and joins them into a flat record for each product. This join is deliberately store-view-specific, because attribute values in Magento can be overridden per store view. Only after this join does the data structure emerge that is subsequently translated into Elasticsearch field names and values by the FieldMapper and BatchDataMapper classes.

Important for understanding the CatalogSearch architecture: the indexer always works in batches, whose size is controllable via the catalog/search/elasticsearch7_indexer_batch_size configuration. For very large catalogs with hundreds of thousands of products, this batch size is a central lever. Batches that are too large overload the Elasticsearch bulk endpoint, batches that are too small extend the overall runtime through too many HTTP roundtrips.

4. The fulltext scope table as an intermediate layer

An often overlooked building block of the CatalogSearch architecture is the fulltext scope table, created per store view in Magento as catalogsearch_fulltext_scope<store_id>. This table acts as a staging area between raw EAV data access and the actual transfer to Elasticsearch. The reason for this intermediate layer is historical: Magento's search index framework was originally designed to be engine-agnostic, so that the native MySQL fulltext search could also use the same data flow. Even though MySQL search is no longer a supported production option in current Magento versions, the scope table remains part of the indexer pipeline.

Inside this scope table sits the processed, store-view-specific text data per product, already merged into a searchable text block from the searchable attribute values. Only from this intermediate layer does the Elasticsearch adapter read the records that are subsequently sent to the search cluster in bulk requests. Anyone inspecting the fulltext scope table directly, for example via SELECT * FROM catalogsearch_fulltext_scope1 LIMIT 5, sees exactly the intermediate state before the data goes through the actual Elasticsearch mapping.

This intermediate layer of the CatalogSearch architecture is also where many reindex problems can be diagnosed: if a product is missing from the search result despite being active and visible, it is worth checking the scope table first. If the record is already missing there, the problem lies in EAV data preparation, not in the Elasticsearch mapping or the query builder. This distinction saves considerable time during troubleshooting.


# Inspect the intermediate fulltext scope table for store id 1
bin/mysql -e "SELECT entity_id, data_index FROM catalogsearch_fulltext_scope1 WHERE entity_id = 12345\G"

# Check whether the indexer is scheduled and its current status
bin/magento indexer:status catalogsearch_fulltext

# Trigger a full reindex for CatalogSearch only
bin/magento indexer:reindex catalogsearch_fulltext

5. From attribute to field: the FieldMapper

Once product data has passed through the fulltext scope intermediate layer, the FieldMapper takes over translating Magento attribute codes into Elasticsearch field names. This step is central to the CatalogSearch architecture because it decides under which field name and with which Elasticsearch data type an attribute ends up in the index. The class Magento\Elasticsearch\Model\Adapter\FieldMapper implements FieldMapperInterface and is assembled differently depending on context (product index or quick search) via a FieldMapperResolver di.xml configuration.

The field type itself is not assigned arbitrarily but derived from the attribute's EAV backend type: a varchar attribute typically becomes a text field with an additional keyword sub-field, a decimal attribute becomes a numeric field, a datetime attribute becomes a date field. This derivation happens in the FieldType resolver classes, registered as an array via di.xml, each supplying a matching Elasticsearch type definition per backend type.


<?php
declare(strict_types=1);

namespace Mironsoft\SearchExtension\Plugin;

use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface;

/**
 * Adds a custom prefix for attributes flagged as "b2b_only"
 * before they reach the Elasticsearch mapping resolver.
 */
class FieldMapperPlugin
{
    /**
     * Intercepts the field name resolution to apply custom naming rules.
     *
     * @param FieldMapperInterface $subject
     * @param string $result
     * @param string $attributeCode
     * @param array $context
     * @return string
     */
    public function afterGetFieldName(
        FieldMapperInterface $subject,
        string $result,
        string $attributeCode,
        array $context = []
    ): string {
        if ($attributeCode === 'b2b_only_price') {
            return 'b2b_price_filter';
        }

        return $result;
    }
}

6. The search request pipeline in detail

While the indexer pipeline writes data into Elasticsearch, the search request pipeline handles the reverse path: a user query string gets translated into a structured Elasticsearch request via Magento\Elasticsearch\SearchAdapter\Query\Builder. This builder class combines the actual search term with filter conditions from layered navigation, store view context, and visibility rules, so that in the end a complete bool query body emerges containing both must and filter clauses.

After sending the request, the SearchAdapter processes the Elasticsearch response and translates hit IDs back into a SearchResult object, which Magento then uses for the product listing. Important in this CatalogSearch architecture: Elasticsearch only returns entity IDs and scores, not full product data. Magento subsequently loads the actual product data normally through the product collection from the database, filtered to the IDs supplied by Elasticsearch. This separation keeps the search index lean and avoids product data having to be maintained twice in two places.


GET /magento2_product_1_v1/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "multi_match": {
            "query": "wireless headphones",
            "fields": ["name^3", "sku^7", "description^1", "short_description^2"],
            "type": "cross_fields"
          }
        }
      ],
      "filter": [
        { "term": { "visibility": 4 } },
        { "term": { "status": 1 } },
        { "term": { "store_id": 1 } }
      ]
    }
  },
  "from": 0,
  "size": 24
}

7. Full reindex vs. schedule: which one applies when

The CatalogSearch architecture supports two fundamentally different update paths. The full reindex rebuilds the entire index from scratch: a new Elasticsearch index with an incremented version suffix is created, all products are written anew, and only at the end is the alias switched to the new index. This approach guarantees consistency but is resource-intensive and takes correspondingly longer for large catalogs.

The partial reindex, triggered via "Update by Schedule," instead processes only the product IDs that have actually changed since the last run. These IDs are collected via a changelog system that Magento automatically populates on every relevant database save. A cron job processes this changelog at configurable intervals and only updates the affected documents in the existing index, without creating a new index. This is the preferred mode for production because changes become visible promptly, without generating the system load of a full reindex.

Component Responsibility Central Class Triggered By
Fulltext indexer Collects EAV data and merges it per store view Indexer\Fulltext\Action\Full Reindex call, cron
Scope table Staging area for processed text data per store catalogsearch_fulltext_scope<id> Indexer run
FieldMapper Translates attribute code to Elasticsearch field name and type Adapter\FieldMapper Before every bulk request
BatchDataMapper Packs raw data into the bulk document structure ProductFieldsMapper Batch processing
Query builder Translates user query into a bool query SearchAdapter\Query\Builder Every search request

8. Diagnosis: making the actual Elasticsearch query visible

A common frustration in the CatalogSearch architecture is that Magento does not display by default which query was actually sent to Elasticsearch. Debugging therefore often happens through workarounds. The most reliable way is to capture HTTP traffic between Magento and the search cluster, for example via a reverse proxy with logging, or via Elasticsearch's own slowlog, which can log even fast queries when the thresholds are set low enough.

Alternatively, the query builder object can be intercepted deliberately via a plugin, writing the generated query to a log file before it is handed to the client. This approach is more invasive but delivers the exact query without network sniffing, and is especially helpful when you suspect that a custom FieldMapper is influencing the query in an unexpected way. Combined with the Elasticsearch _explain API, you can then trace why a particular document is not matched at all, or matched with an unexpectedly low score, for a given query.


POST /magento2_product_1_v1/_explain/12345
{
  "query": {
    "multi_match": {
      "query": "wireless headphones",
      "fields": ["name^3", "sku^7", "description^1"]
    }
  }
}

9. Practical example: a custom indexer extension

A realistic example of extending the CatalogSearch architecture is enriching the search index with computed values that do not exist as a product attribute directly, for instance a "popularity score" derived from order data. Instead of maintaining this value as a classic EAV attribute, it can be computed directly in the indexer pipeline and inserted as an additional field into the Elasticsearch document, without changing the database structure.

To do this, a plugin is placed on the BatchDataMapper class that appends the computed score after the standard field assembly. Important here: the new field name also needs to exist as a dedicated field in the Elasticsearch mapping, otherwise Elasticsearch's dynamic mapping detection kicks in, which does not always choose the intended field type. An explicit mapping entry before the next full reindex reliably prevents this uncertainty.

This kind of extension shows the real benefit of the modular CatalogSearch architecture: you do not need to reimplement either the indexer or the query builder from scratch, but hook into a clearly defined extension point without jeopardizing compatibility with Magento core updates.

Mironsoft

Magento CatalogSearch, Elasticsearch and OpenSearch consulting

Want Magento search that stays technically traceable?

We analyze your CatalogSearch architecture, find the root cause of missing results, and build custom FieldMapper and indexer extensions that stay compatible with every Magento update.

Architecture audit

Reviewing indexer, FieldMapper and query builder for weak points

Custom extensions

Custom FieldMapper and indexer plugins for specific requirements

Performance tuning

Optimizing batch sizes, reindex strategies and cluster configuration

10. Summary

The Magento CatalogSearch architecture is not a single configuration toggle but a multi-stage pipeline made up of the indexer, the fulltext scope intermediate layer, the FieldMapper and the search request builder. Product data passes through several clearly separated stages during indexing before ending up as an Elasticsearch document, and the same path is traversed in reverse on every search request, to build structured Elasticsearch queries from user input.

Anyone who understands this CatalogSearch architecture finds error sources significantly faster: if a product is missing from the result, check the scope table first, then the mapping, then the query builder. Extensions such as custom FieldMapper plugins or additional computed fields can be attached at clearly defined extension points without touching the indexer's core logic. It is exactly this separation of concerns that keeps the architecture maintainable despite its complexity.

Magento CatalogSearch architecture: the essentials at a glance

Indexer pipeline

EAV data is merged per store view, written to the fulltext scope table and sent to Elasticsearch in batches.

FieldMapper

Translates attribute codes into Elasticsearch field names and data types based on the EAV backend type.

Reindex modes

Full reindex rebuilds the entire index, Update by Schedule updates only changed product IDs via a changelog.

Extensibility

FieldMapper and BatchDataMapper plugins allow custom fields without touching the indexer core.

11. FAQ: Magento CatalogSearch Architecture

1What does the catalogsearch_fulltext indexer do?
Collects EAV attribute values per store view, merges them, writes them into the scope table, and sends them to Elasticsearch in batches.
2What is the fulltext scope table for?
A store-view-specific intermediate layer before transfer to Elasticsearch. The first place to check for missing search hits.
3Elasticsearch vs. Elasticsearch7 module?
Generic base versus version-specific client encapsulation. Most extensions hook into the generic base.
4How is the field type determined?
From the EAV backend type: varchar to text plus keyword, decimal to numeric, datetime to date.
5Full reindex or schedule?
Full after mapping changes or bulk import, schedule for ongoing production operation.
6How do I see the real ES query?
Traffic capture, slowlog with a low threshold, or a logging plugin on the query builder.
7Custom computed fields possible?
Yes, via a plugin on BatchDataMapper, with a matching explicit mapping entry.
8Why only IDs instead of product data?
Lean index, no duplicate data maintenance. Magento loads the actual product data from the database afterward.
9What controls the batch size?
catalog/search/elasticsearch7_indexer_batch_size. A balance between bulk load and roundtrip count.
10Where to hook custom mapping?
At FieldMapperInterface or via a plugin on it, engine-agnostic and update-safe.