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.
Table of Contents
- 1. CatalogSearch as architecture, not a feature
- 2. The Elasticsearch modules at a glance
- 3. The indexer pipeline: from EAV attribute to document
- 4. The fulltext scope table as an intermediate layer
- 5. From attribute to field: the FieldMapper
- 6. The search request pipeline in detail
- 7. Full reindex vs. schedule: which one applies when
- 8. Diagnosis: making the actual Elasticsearch query visible
- 9. Practical example: a custom indexer extension
- 10. Summary
- 11. FAQ
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.