Customizing Magento Search Autocomplete Suggestions on the Backend
AI generated
M2
di.xml
Magento 2
Customizing Search Autocomplete Suggestions on the Backend
Adding custom data sources and keeping performance in check during fast typing

Autocomplete suggestions in the search box look like a pure frontend feature, but they are assembled entirely server side from a set of data providers. This article explains how those providers work together, how to add a custom source such as CMS pages, and which levers actually determine how smooth suggestions feel while a customer is typing quickly.

10 min read Autocomplete Data provider Performance GraphQL

1. Why autocomplete is not purely a frontend concern

Anyone who thinks of autocomplete primarily in terms of debounce timers and JavaScript is missing the actual work: Magento already assembles and sorts the suggestion list on the server, and the frontend simply renders whatever it receives. The interesting levers, which data sources feed in, how many results per source, and in what order, all live on the backend.

That clearly separates this topic from layered navigation or general full text relevance tuning, which are covered elsewhere. Autocomplete runs through its own, leaner request path that deliberately skips the full search index with all its aggregations and is instead optimized purely for response speed.

That speed optimization is exactly what makes the topic relevant for backend developers: every additional data source, every additional field in the result list costs milliseconds that add up again on every single keystroke.

2. The data provider architecture at a glance

The central entry point is Magento\Search\Model\Autocomplete, which iterates over a configured list of data providers and merges their results into one shared suggestion list. Each provider implements DataProviderInterface with a single method, getItems, returning a list of ItemInterface objects, typically carrying a title and a target URL.

Out of the box, two providers are active: one for product suggestions based on the search index, and one for popular or recently used search terms based on the search_query table. Categories and CMS pages are deliberately not part of the default set, so anyone wanting that content in the suggestions has to add it through a custom provider.

The order of providers in the configuration directly determines the order in the suggestion list, a frequently overlooked lever for, say, showing product suggestions ahead of popular search terms on purpose.


<type name="Magento\Search\Model\Autocomplete">
    <arguments>
        <argument name="dataProviders" xsi:type="array">
            <item name="popularQueries" xsi:type="object">Magento\Search\Model\Autocomplete\DataProvider</item>
            <item name="products" xsi:type="object">Magento\CatalogSearch\Model\Autocomplete\DataProvider</item>
            <item name="cmsPages" xsi:type="object">Mironsoft\SearchAutocomplete\Model\Autocomplete\CmsPageDataProvider</item>
        </argument>
    </arguments>
</type>

3. Implementing a custom provider for CMS pages

A custom provider for CMS pages follows the same pattern as the built in ones: getItems reads the current search term, filters a CMS page collection by title and returns a bounded number of matches as ItemInterface objects. It is important to explicitly restrict the collection to active pages visible on the current store, since unlike products, CMS pages have no per customer group visibility attribute of their own.

The result count should stay small, three to five matches are entirely sufficient for a suggestion list. An unfiltered LIKE query across the full CMS page title index can become noticeably slower than the already index backed product search as page count grows, so a simple title index on its own column pays off once more than a few hundred pages are maintained.


<?php

declare(strict_types=1);

namespace Mironsoft\SearchAutocomplete\Model\Autocomplete;

use Magento\Cms\Model\ResourceModel\Page\CollectionFactory;
use Magento\Search\Model\Autocomplete\DataProviderInterface;
use Magento\Search\Model\Autocomplete\ItemFactory;
use Magento\Search\Model\QueryFactory;

/**
 * Provides CMS pages as an additional data source for the autocomplete suggestion list.
 */
class CmsPageDataProvider implements DataProviderInterface
{
    private const RESULT_LIMIT = 4;

    /**
     * @param CollectionFactory $collectionFactory
     * @param ItemFactory $itemFactory
     * @param QueryFactory $queryFactory
     */
    public function __construct(
        private readonly CollectionFactory $collectionFactory,
        private readonly ItemFactory $itemFactory,
        private readonly QueryFactory $queryFactory,
    ) {
    }

    /**
     * Looks up active CMS pages matching the current search term and returns them as suggestion items.
     *
     * @return \Magento\Search\Model\Autocomplete\ItemInterface[]
     */
    public function getItems(): array
    {
        $queryText = $this->queryFactory->get()->getQueryText();
        $collection = $this->collectionFactory->create();
        $collection->addFieldToFilter('is_active', ['eq' => 1])
            ->addFieldToFilter('title', ['like' => '%' . $queryText . '%'])
            ->setPageSize(self::RESULT_LIMIT);

        $items = [];
        foreach ($collection as $page) {
            $items[] = $this->itemFactory->create(['title' => $page->getTitle()]);
        }

        return $items;
    }
}

4. The GraphQL path for storefronts without a classic autocomplete block

Storefronts built consistently on GraphQL do not fetch suggestions through the REST leaning autocomplete controller, but through a products query with a reduced field set, complemented by a dedicated, lean query for CMS suggestions. That approach makes it possible to bundle product and CMS suggestions into a single request, instead of firing two separate requests on every keystroke.

It is important to only request the fields the suggestion list actually needs in the GraphQL query, meaning name, url_key and a small image, rather than the full product data with every attribute. Every additional field increases resolver time on the server, which is immediately noticeable for a feature that reacts to every single keystroke.


query AutocompleteSuggestions($search: String!) {
  products(search: $search, pageSize: 5) {
    items {
      name
      url_key
      small_image {
        url
      }
    }
  }
}

5. Server side limits and caching for suggestions

The number of suggestions per request is controlled through the configuration path catalog/search/autocomplete_limit and directly affects response time, since every additional match means an additional database or index operation. A limit of five to eight results is sufficient for most stores in practice and noticeably faster than twenty or more matches.

Since the customer account runs through private content but autocomplete requests mostly return the same data regardless of login state, a brief, few seconds long HTTP cache for the response at the full page cache layer or an upstream reverse proxy is worth it, especially for the most common search terms, which stay nearly identical anyway.

6. Weighing server side against client side debounce

Client side debounce, meaning delaying the request by typically two hundred to three hundred milliseconds after the last keystroke, reduces how many requests even reach the server. That is the single most effective lever, because every avoided request saves its server time entirely, while server side optimizations only make the remaining requests faster, not fewer.

On the server side, a simple per session request throttle helps as a complement, for example a short cache key built from search term and store that catches identical, closely spaced requests before they hit the data sources again. That guards against edge cases such as duplicated events or users typing the same term twice in quick succession, for instance through copy paste.

In practice, a combination of moderate client side debounce and a tight server side limit is far more effective than trying to make every single request faster purely through backend optimization, since the fastest request is always the one that never gets made in the first place.

7. Multiple providers inside a shared performance budget

Once several data providers run in parallel, say products, popular search terms and a custom CMS source, their individual times add up inside Autocomplete::getItems, since providers are queried sequentially by default. A single slow provider therefore delays the entire response, even if the remaining providers would have been done long ago.

For custom providers, a deliberate time budget is worth building in, for example a hard cap on the collection query via setPageSize and an index instead of a LIKE query on the search field. Anyone adding several extra providers should regularly measure their combined runtime against the original response time with only the default providers active, to catch gradual slowdowns early.

8. Measuring and monitoring autocomplete response time

Without measurement, any claim about perceived speed stays speculative. A simple approach is a timing log built directly into Autocomplete that records each provider's runtime separately, instead of only capturing the total request time. That makes it immediately obvious which provider is actually responsible for a slowdown.

For production, it is worth forwarding these values to existing monitoring such as New Relic or a self hosted APM tool, with a warning threshold for the total autocomplete response time. Experience suggests that threshold should sit well under two hundred milliseconds, since anything above that gets perceived as noticeable lag during fast typing.

9. Edge cases and the boundary with full text search

Very short search terms of one or two characters should, in most cases, not trigger a request to the providers at all, since the result set is too unspecific to yield useful suggestions anyway while still generating unnecessary server load. A minimum length of three characters, configurable client side, has proven effective in practice.

The boundary with the actual results page after submitting a search remains important: autocomplete deliberately delivers a reduced, fast preview, while the full results page with filters, facets and complete relevance scoring runs through the regular search index. That separation should be preserved through every extension of the autocomplete backend, so the suggestion field's speed does not get burdened with functionality that really belongs on the results page.

Data source Active by default? Typical latency Recommended limit
Products (catalogsearch) Yes low, index backed 5 to 8 matches
Popular search terms Yes very low 3 to 5 matches
CMS pages (custom provider) No medium without index 3 to 4 matches
Categories (custom provider) No low, index backed 3 matches
Customer specific suggestions No high without caching not recommended without caching

Mironsoft

Magento development, module consulting, and system architecture

A Magento project that needs a second opinion or experienced execution?

We build custom Magento modules, advise on architecture decisions, and take on complex implementations, from service contract planning to production-ready deployment.

Architecture Consulting

Have module and system architecture thought through properly before you build.

Custom Module Development

Build custom Magento modules cleanly, following best practices.

Code Review & Audit

Have existing modules reviewed for performance, security, and maintainability.

10. Summary

Autocomplete backend

Architecture

DataProviderInterface implementations return items that Autocomplete::getItems merges into one shared list.

Extension

Custom providers such as CMS pages get added through di.xml and follow the same interface as the default providers.

Performance

Client side debounce eliminates requests entirely, server side limits and indexing speed up the ones that remain.

Monitoring

Per provider timing logs surface slow data sources before they turn into noticeable lag.

11. FAQ: Autocomplete backend

1Are categories part of Magento's autocomplete suggestions by default?
No, only products and popular search terms are active by default, categories and CMS pages need to be added through a custom data provider.
2Which method does a custom autocomplete provider need to implement?
DataProviderInterface with the method getItems, returning a list of ItemInterface objects carrying a title and target URL.
3Where do I control the order of data sources within the suggestions?
Through the order of entries in the dataProviders array in di.xml, the configuration order directly determines the display order.
4What does the configuration path catalog/search/autocomplete_limit control?
The maximum number of suggestions per request, a lower value directly reduces response time since fewer matches get processed.
5Is client side or server side debounce more effective?
Client side debounce is the more effective lever, because avoided requests disappear entirely rather than merely being processed faster.
6How fast should an autocomplete response ideally be?
Well under two hundred milliseconds, anything above that gets perceived as noticeable lag during fast typing.
7Why should a CMS page provider avoid an unfiltered LIKE query?
Because an unfiltered LIKE query becomes noticeably slower as page count grows compared to an index backed search, a simple title index helps once page count reaches a few hundred.
8Does autocomplete run through the same request path as the full search results page?
No, autocomplete uses a leaner, speed optimized path without the full facet and relevance calculation used on the results page.
9At what character length should an autocomplete request even fire?
Experience suggests three characters, shorter input yields overly unspecific results and generates unnecessary server load.
10How do I identify which provider is responsible for a slow autocomplete response?
Through per provider timing logs inside Autocomplete::getItems that record each provider's runtime individually instead of only the total time.