By default, the Magento 2 admin product grid has no category column and no way to filter products by category — which gets painful fast on a large catalog. In this guide we'll add a custom Categories column to the product grid, complete with a dropdown filter, by building a small module step by step.
Steps to Add a Custom Filter to the Magento 2 Product Grid
Step 1: Create registration.php
Create registration.php in app/code/Mageants/Blog:
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Mageants_Blog',
__DIR__
);
|
Step 2: Create module.xml
Create module.xml in app/code/Mageants/Blog/etc/:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Mageants_Blog" setup_version="1.0.0"/>
</config>
|
Step 3: Create ProductDataProvider.php
Create ProductDataProvider.php in app/code/Mageants/Blog/Ui/DataProvider/Product/. This extends the core product data provider and teaches it how to filter on category_id:
<?php
namespace Mageants\Blog\Ui\DataProvider\Product;
class ProductDataProvider extends \Magento\Catalog\Ui\DataProvider\Product\ProductDataProvider
{
public function addFilter(\Magento\Framework\Api\Filter $filter)
{
if ($filter->getField() == 'category_id') {
$this->getCollection()->addCategoriesFilter(['in' => $filter->getValue()]);
} elseif (isset($this->addFilterStrategies[$filter->getField()])) {
$this->addFilterStrategies[$filter->getField()]
->addFilter(
$this->getCollection(),
$filter->getField(),
[$filter->getConditionType() => $filter->getValue()]
);
} else {
parent::addFilter($filter);
}
}
}
|
Step 4: Create di.xml
Create di.xml in app/code/Mageants/Blog/etc/ to make Magento use our data provider in place of the core one:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Magento\Catalog\Ui\DataProvider\Product\ProductDataProvider"
type="Mageants\Blog\Ui\DataProvider\Product\ProductDataProvider"/>
</config>
|
Step 5: Create CategoryList.php (Filter Options)
Create CategoryList.php in app/code/Mageants/Blog/Model/Category/. This supplies the dropdown options for the filter — one entry per category:
<?php
namespace Mageants\Blog\Model\Category;
use Magento\Framework\Option\ArrayInterface;
use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory;
class CategoryList implements ArrayInterface
{
protected $categoryCollectionFactory;
public function __construct(
CollectionFactory $categoryCollectionFactory
) {
$this->categoryCollectionFactory = $categoryCollectionFactory;
}
public function toOptionArray($addEmpty = true)
{
$collection = $this->categoryCollectionFactory->create();
$collection->addAttributeToSelect('name');
$options = [];
if ($addEmpty) {
$options[] = ['label' => __('-- Please Select a Category --'), 'value' => ''];
}
foreach ($collection as $category) {
$options[] = ['label' => $category->getName(), 'value' => $category->getId()];
}
return $options;
}
}
|
Step 6: Create the Category Column Class
Create Category.php in app/code/Mageants/Blog/Ui/Component/Listing/Column/. This fills the new column with each product's category names. Rather than loading each product and category through the object manager inside a loop (slow on a big grid), we inject the category collection factory and build an ID → name map once:
<?php
namespace Mageants\Blog\Ui\Component\Listing\Column;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Ui\Component\Listing\Columns\Column;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory;
class Category extends Column
{
protected $productRepository;
protected $categoryCollectionFactory;
public function __construct(
ContextInterface $context,
UiComponentFactory $uiComponentFactory,
ProductRepositoryInterface $productRepository,
CollectionFactory $categoryCollectionFactory,
array $components = [],
array $data = []
) {
$this->productRepository = $productRepository;
$this->categoryCollectionFactory = $categoryCollectionFactory;
parent::__construct($context, $uiComponentFactory, $components, $data);
}
public function prepareDataSource(array $dataSource)
{
if (!isset($dataSource['data']['items'])) {
return $dataSource;
}
$fieldName = $this->getData('name');
// Build an id => name map for all categories once
$categoryNames = [];
$collection = $this->categoryCollectionFactory->create()->addAttributeToSelect('name');
foreach ($collection as $category) {
$categoryNames[$category->getId()] = $category->getName();
}
foreach ($dataSource['data']['items'] as & $item) {
$product = $this->productRepository->getById($item['entity_id']);
$names = [];
foreach ($product->getCategoryIds() as $catId) {
if (isset($categoryNames[$catId])) {
$names[] = $categoryNames[$catId];
}
}
$item[$fieldName] = implode(', ', $names);
}
return $dataSource;
}
}
|
Step 7: Create product_listing.xml
Create product_listing.xml in app/code/Mageants/Blog/view/adminhtml/ui_component/. This adds the column to the grid and wires up the dropdown filter with our options class:
<?xml version="1.0"?>
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
<columns name="product_columns" class="Magento\Catalog\Ui\Component\Listing\Columns">
<column name="category_id" class="Mageants\Blog\Ui\Component\Listing\Column\Category">
<argument name="data" xsi:type="array">
<item name="options" xsi:type="object">Mageants\Blog\Model\Category\CategoryList</item>
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">select</item>
<item name="add_field" xsi:type="boolean">true</item>
<item name="label" xsi:type="string" translate="true">Categories</item>
<item name="sortOrder" xsi:type="number">75</item>
<item name="dataType" xsi:type="string">select</item>
</item>
</argument>
</column>
</columns>
</listing>
|
Step 8: Deploy
Run these commands, then open the admin product grid — you'll see the new Categories column and its dropdown filter:
php bin/magento setup:upgrade php bin/magento setup:di:compile php bin/magento setup:static-content:deploy -f php bin/magento cache:flush |
In the admin, go to Catalog > Products, open Filters, and you'll find the new Categories filter alongside the column.
Conclusion
A custom column and filter make the admin product grid far easier to work with on a large catalog — here we added category filtering, but the same UI component pattern (data provider filter, options class, column class, ui_component XML) extends to any attribute you want to filter or display. The two things that break this most often are curly "smart quotes" pasted into PHP and a malformed ui_component XML tag, so watch both.
Admin-side filtering is one half of the picture; the other is helping customers find products on the storefront. If your category and search pages need better sorting and filtering options for shoppers, the Improved Sorting extension adds custom sort orders (best sellers, new arrivals, top rated, and more) so visitors reach the right products faster — the customer-facing counterpart to the admin filtering we built above.