Magento 2: Update Elasticsearch Query for Category Products

When building custom features in Magento 2, one common requirement is to show only specific products inside a category listing page — even though the category might contain many more.
For example, maybe you want to highlight only a hand-picked set of product IDs for a campaign or restrict visibility based on business rules.
Magento provides a flexible search and listing mechanism powered by Elasticsearch (in 2.4+).
To achieve this, we can intercept the search request and inject our own product ID filter into the Elasticsearch query.
In this blog, I’ll walk you through how to do it step by step.
Step 1: Understand the Flow
Magento’s category product listing uses the catalog search layer internally, even when browsing categories. The queries go through:
\Magento\CatalogSearch\Model\Search\RequestGenerator(builds search queries)\Magento\Framework\Search\AdapterInterface::query()(executes the query in Elasticsearch)
If we want to control which products show up, the cleanest way is to make a plugin on the query() method of AdapterInterface. There we can inject our allowed product IDs into the request.
Step 2: Create a Custom Module Skeleton
Let’s create a simple module, for example:Vendor/CustomFilter
app/code/Vendor/CustomFilter/ ├── etc │ └── di.xml ├── Plugin │ └── SearchAdapterPlugin.php ├── registration.php └── etc/module.xml
Step 3: Define the Plugin in di.xml
In etc/di.xml, declare a plugin on the AdapterInterface:
<?php
namespace Vendor\CustomFilter\Plugin;
use Magento\Framework\Search\RequestInterface;
use Magento\Elasticsearch\SearchAdapter\Aggregation\Builder as AggregationBuilder;
use Magento\Elasticsearch\SearchAdapter\ConnectionManager;
use Magento\Elasticsearch\SearchAdapter\ResponseFactory;
use Magento\Elasticsearch\ElasticAdapter\SearchAdapter\Mapper;
use Magento\Elasticsearch\SearchAdapter\QueryContainerFactory;
class SearchAdapterPlugin
{
public function __construct(
ConnectionManager $connectionManager,
ResponseFactory $responseFactory,
AggregationBuilder $aggregationBuilder,
Mapper $mapper,
QueryContainerFactory $queryContainerFactory,
\Magento\Framework\App\RequestInterface $request,
\Magento\Framework\App\ResourceConnection $resource
) {
$this->connectionManager = $connectionManager;
$this->responseFactory = $responseFactory;
$this->aggregationBuilder = $aggregationBuilder;
$this->mapper = $mapper;
$this->queryContainerFactory = $queryContainerFactory;
$this->request = $request;
$this->connection = $resource->getConnection();
}
/**
* Around plugin for Elasticsearch query
*/
public function aroundQuery(
\Magento\Framework\Search\AdapterInterface $subject,
callable $proceed,
RequestInterface $request
) {
// Example: Allowed product IDs (could come from config, DB, or business logic)
$allowedProductIds = [12, 34, 56, 78];
$client = $this->connectionManager->getConnection();
$aggregationBuilder = $this->aggregationBuilder;
$updatedQuery = $this->mapper->buildQuery($request);
if (!isset($updatedQuery['body']['query']['bool'])) {
$updatedQuery['body']['query']['bool'] = [];
}
if (!isset($updatedQuery['body']['query']['bool']['filter'])) {
$updatedQuery['body']['query']['bool']['filter'] = [];
}
$updatedQuery['body']['query']['bool']['filter'][] = ['ids' => ['values' => $allowedProductIds]];
$aggregationBuilder->setQuery($this->queryContainerFactory->create(['query' => $updatedQuery]));
$rawResponse = $client->query($updatedQuery);
$rawDocuments = $rawResponse['hits']['hits'] ?? [];
$queryResponse = $this->responseFactory->create(
[
'documents' => $rawDocuments,
'aggregations' => $aggregationBuilder->build($request, $rawResponse),
'total' => $rawResponse['hits']['total']['value'] ?? 0
]
);
// Now proceed with modified query response
return $queryResponse;
}
}
Here’s what’s happening:
- We define an around plugin for
query() - Before passing the request to Elasticsearch, we inject an IDs filter
- Elasticsearch then returns only those product IDs in the results
Step 5: Clear Cache & Test
Run the usual setup commands:
bin/magento setup:upgrade bin/magento cache:flush
Now visit any category page — instead of all products, you’ll only see the ones with IDs [12, 34, 56, 78].
Conclusion
Filtering products by specific IDs in Magento 2 gives you greater control over what appears in category listings. It’s especially useful for highlighting featured or campaign-based products.
Along with filtering, you may also want to customize how products are ordered. For example, you can sort them by the lowest available variation price.
Learn how in our related guide: Sort Category Listing Products by Custom Price.