Edit in GitHubLog an issue
Thanks to Goivvy LLC for contributing this topic!

Add an Admin grid

Admin grids are used to represent, filter, and sort various data in the Adobe Commerce and Magento Open Source Admin application. They are also used to perform mass actions such as updates and deletes. This tutorial will show you how to create a simple Admin grid.

1. Create a backbone module#

Everything starts with a module. Dev_Grid will be used as the namespace:

Copied to your clipboard
mkdir -p app/code/Dev/Grid/etc

Here are the required files to get started:

app/code/Dev/Grid/etc/module.xml:

Copied to your clipboard
1<?xml version="1.0"?>
2<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
3 <module name="Dev_Grid">
4 <sequence>
5 <module name="Magento_Backend"/>
6 <module name="Magento_Ui"/>
7 </sequence>
8 </module>
9</config>

app/code/Dev/Grid/registration.php:

Copied to your clipboard
1<?php
2/**
3 * Copyright © Magento, Inc. All rights reserved.
4 * See COPYING.txt for license details.
5 */
6
7use Magento\Framework\Component\ComponentRegistrar;
8
9ComponentRegistrar::register(
10 ComponentRegistrar::MODULE,
11 'Dev_Grid',
12 __DIR__
13);

app/code/Dev/Grid/etc/adminhtml/routes.xml:

Copied to your clipboard
1<?xml version="1.0"?>
2<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
3 <router id="admin">
4 <route id="dev_grid" frontName="dev_grid">
5 <module name="Dev_Grid" before="Magento_Backend" />
6 </route>
7 </router>
8</config>

app/code/Dev/Grid/etc/adminhtml/menu.xml:

Copied to your clipboard
1<?xml version="1.0"?>
2<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
3 <menu>
4 <add id="Dev_Grid::home" title="Category Listing" module="Dev_Grid" sortOrder="1000" parent="Magento_Catalog::catalog_categories" resource="Magento_Catalog::categories" action="dev_grid/index/index"/>
5 </menu>
6</config>

2. Define the Admin grid#

The grid displays a list of available categories that start with a letter b or B. This grid has three columns: ID, category path and category name. ID and category path are from the catalog_category_entity table. For the name values, joins are used. The page layout file is app/code/Dev/Grid/view/adminhtml/layout/dev_grid_index_index.xml:

Copied to your clipboard
1<?xml version="1.0"?>
2<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
3 <body>
4 <referenceContainer name="content">
5 <uiComponent name="dev_grid_category_listing"/>
6 </referenceContainer>
7 </body>
8</page>

The UI component dev_grid_category_listing must be defined separately in a file with the same name, ending with .xml - app/code/Dev/Grid/view/adminhtml/ui_component/dev_grid_category_listing.xml:

Copied to your clipboard
1<?xml version="1.0" encoding="UTF-8"?>
2<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
3 <argument name="data" xsi:type="array">
4 <item name="js_config" xsi:type="array">
5 <item name="provider" xsi:type="string">dev_grid_category_listing.dev_grid_category_listing_data_source</item>
6 <item name="deps" xsi:type="string">dev_grid_category_listing.dev_grid_category_listing_data_source</item>
7 </item>
8 <item name="spinner" xsi:type="string">dev_grid_category_columns</item>
9 <item name="buttons" xsi:type="array">
10 <item name="add" xsi:type="array">
11 <item name="name" xsi:type="string">add</item>
12 <item name="label" xsi:type="string">View Category Tree</item>
13 <item name="class" xsi:type="string">primary</item>
14 <item name="url" xsi:type="string">catalog/category/index</item>
15 </item>
16 </item>
17 </argument>
18 <dataSource name="dev_grid_category_listing_data_source">
19 <argument name="dataProvider" xsi:type="configurableObject">
20 <argument name="class" xsi:type="string">Dev\Grid\Ui\DataProvider\Category\ListingDataProvider</argument>
21 <argument name="name" xsi:type="string">dev_grid_category_listing_data_source</argument>
22 <argument name="primaryFieldName" xsi:type="string">entity_id</argument>
23 <argument name="requestFieldName" xsi:type="string">entity_id</argument>
24 <argument name="data" xsi:type="array">
25 <item name="config" xsi:type="array">
26 <item name="update_url" xsi:type="url" path="mui/index/render"/>
27 <item name="storageConfig" xsi:type="array">
28 <item name="indexField" xsi:type="string">entity_id</item>
29 </item>
30 </item>
31 </argument>
32 </argument>
33 <argument name="data" xsi:type="array">
34 <item name="js_config" xsi:type="array">
35 <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
36 </item>
37 </argument>
38 </dataSource>
39 <listingToolbar name="listing_top">
40 <bookmark name="bookmarks"/>
41 <columnsControls name="columns_controls"/>
42 <massaction name="listing_massaction">
43 <argument name="data" xsi:type="array">
44 <item name="data" xsi:type="array">
45 <item name="selectProvider" xsi:type="string">dev_grid_category_listing.dev_grid_category_listing.dev_grid_category_columns.ids</item>
46 <item name="displayArea" xsi:type="string">bottom</item>
47 <item name="component" xsi:type="string">Magento_Ui/js/grid/tree-massactions</item>
48 <item name="indexField" xsi:type="string">entity_id</item>
49 </item>
50 </argument>
51 <action name="delete">
52 <argument name="data" xsi:type="array">
53 <item name="config" xsi:type="array">
54 <item name="type" xsi:type="string">delete</item>
55 <item name="label" xsi:type="string" translate="true">Delete</item>
56 <item name="url" xsi:type="url" path="dev_grid/category/massDelete"/>
57 <item name="confirm" xsi:type="array">
58 <item name="title" xsi:type="string" translate="true">Delete items</item>
59 <item name="message" xsi:type="string" translate="true">Are you sure you want to delete selected items?</item>
60 </item>
61 </item>
62 </argument>
63 </action>
64 </massaction>
65 <filters name="listing_filters">
66 <argument name="data" xsi:type="array">
67 <item name="config" xsi:type="array">
68 <item name="templates" xsi:type="array">
69 <item name="filters" xsi:type="array">
70 <item name="select" xsi:type="array">
71 <item name="component" xsi:type="string">Magento_Ui/js/form/element/ui-select</item>
72 <item name="template" xsi:type="string">ui/grid/filters/elements/ui-select</item>
73 </item>
74 </item>
75 </item>
76 </item>
77 </argument>
78 </filters>
79 <paging name="listing_paging"/>
80 </listingToolbar>
81 <columns name="dev_grid_category_columns">
82 <selectionsColumn name="ids">
83 <argument name="data" xsi:type="array">
84 <item name="config" xsi:type="array">
85 <item name="indexField" xsi:type="string">entity_id</item>
86 </item>
87 </argument>
88 </selectionsColumn>
89 <column name="entity_id">
90 <settings>
91 <filter>textRange</filter>
92 <label translate="true">ID</label>
93 <resizeDefaultWidth>25</resizeDefaultWidth>
94 </settings>
95 </column>
96 <column name="path">
97 <settings>
98 <filter>text</filter>
99 <bodyTmpl>ui/grid/cells/text</bodyTmpl>
100 <label translate="true">Path</label>
101 </settings>
102 </column>
103 <column name="name">
104 <settings>
105 <filter>text</filter>
106 <bodyTmpl>ui/grid/cells/text</bodyTmpl>
107 <label translate="true">Name</label>
108 </settings>
109 </column>
110 <column name="created_at" class="Magento\Ui\Component\Listing\Columns\Date" component="Magento_Ui/js/grid/columns/date">
111 <settings>
112 <filter>dateRange</filter>
113 <dataType>date</dataType>
114 <label translate="true">Created</label>
115 </settings>
116 </column>
117 <actionsColumn name="actions" class="Dev\Grid\Ui\Component\Category\Listing\Column\Actions" sortOrder="200">
118 <argument name="data" xsi:type="array">
119 <item name="config" xsi:type="array">
120 <item name="resizeEnabled" xsi:type="boolean">false</item>
121 <item name="resizeDefaultWidth" xsi:type="string">107</item>
122 <item name="indexField" xsi:type="string">entity_id</item>
123 </item>
124 </argument>
125 <argument name="viewUrl" xsi:type="string">catalog/category/view</argument>
126 </actionsColumn>
127 </columns>
128</listing>

This file consists of several sections:

  • dataSource - references the class that is responsible for getting the requested data.
  • listingToolbar - where mass actions and filters are defined.
  • columns - lists the columns to be displayed.

3. Define the data source class#

The UI references Dev\Grid\Ui\DataProvider\Category\ListingDataProvider as the data source class.

The corresponding file is app/code/Dev/Grid/Ui/DataProvider/Category/ListingDataProvider.php:

Copied to your clipboard
1<?php
2/**
3 * Copyright © Magento, Inc. All rights reserved.
4 * See COPYING.txt for license details.
5 */
6
7namespace Dev\Grid\Ui\DataProvider\Category;
8
9use Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider;
10
11class ListingDataProvider extends DataProvider
12{
13}

It has to extend \Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider. The plugin then gets a name attribute:

app/code/Dev/Grid/etc/di.xml:

Copied to your clipboard
1<?xml version="1.0"?>
2<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
3 <type name="Dev\Grid\Ui\DataProvider\Category\ListingDataProvider">
4 <plugin name="dev_grid_attributes" type="Dev\Grid\Plugin\AddAttributesToUiDataProvider"/>
5 </type>
6 <type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
7 <arguments>
8 <argument name="collections" xsi:type="array">
9 <item name="dev_grid_category_listing_data_source" xsi:type="string">DevGridCategoryCollection</item>
10 </argument>
11 </arguments>
12 </type>
13 <virtualType name="DevGridCategoryCollection" type="Dev\Grid\Ui\DataProvider\Category\Listing\Collection">
14 <arguments>
15 <argument name="mainTable" xsi:type="string">catalog_category_entity</argument>
16 <argument name="resourceModel" xsi:type="string">Dev\Grid\Model\ResourceModel\Category</argument>
17 </arguments>
18 </virtualType>
19</config>

app/code/Dev/Grid/Plugin/AddAttributesToUiDataProvider.php:

Copied to your clipboard
1<?php
2/**
3 * Copyright © Magento, Inc. All rights reserved.
4 * See COPYING.txt for license details.
5 */
6
7namespace Dev\Grid\Plugin;
8
9use Dev\Grid\Ui\DataProvider\Category\ListingDataProvider as CategoryDataProvider;
10use Magento\Eav\Api\AttributeRepositoryInterface;
11use Magento\Framework\App\ProductMetadataInterface;
12use Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult;
13
14class AddAttributesToUiDataProvider
15{
16 /** @var AttributeRepositoryInterface */
17 private $attributeRepository;
18
19 /** @var ProductMetadataInterface */
20 private $productMetadata;
21
22 /**
23 * Constructor
24 *
25 * @param AttributeRepositoryInterface $attributeRepository
26 * @param ProductMetadataInterface $productMetadata
27 */
28 public function __construct(
29 AttributeRepositoryInterface $attributeRepository,
30 ProductMetadataInterface $productMetadata
31 ) {
32 $this->attributeRepository = $attributeRepository;
33 $this->productMetadata = $productMetadata;
34 }
35
36 /**
37 * Get Search Result after plugin
38 *
39 * @param CategoryDataProvider $subject
40 * @param SearchResult $result
41 * @return SearchResult
42 */
43 public function afterGetSearchResult(CategoryDataProvider $subject, SearchResult $result)
44 {
45 if ($result->isLoaded()) {
46 return $result;
47 }
48
49 $edition = $this->productMetadata->getEdition();
50
51 $column = 'entity_id';
52
53 if ($edition == 'Enterprise') {
54 $column = 'row_id';
55 }
56
57 $attribute = $this->attributeRepository->get('catalog_category', 'name');
58
59 $result->getSelect()->joinLeft(
60 ['devgridname' => $attribute->getBackendTable()],
61 'devgridname.' . $column . ' = main_table.' . $column . ' AND devgridname.attribute_id = '
62 . $attribute->getAttributeId(),
63 ['name' => 'devgridname.value']
64 );
65
66 $result->getSelect()->where('devgridname.value LIKE "B%"');
67
68 return $result;
69 }
70}

This works with both enterprise and community versions by linking on different fields. In this case, LIKE is case insensitive.

4. Data source collection#

The dataSource name dev_grid_category_listing_data_source links to Dev\Grid\Ui\DataProvider\Category\Listing\Collection collection in app/code/Dev/Grid/etc/di.xml.

di.xml also sets the main table and resource model:

Copied to your clipboard
1 <virtualType name="DevGridCategoryCollection" type="Dev\Grid\Ui\DataProvider\Category\Listing\Collection">
2 <arguments>
3 <argument name="mainTable" xsi:type="string">catalog_category_entity</argument>
4 <argument name="resourceModel" xsi:type="string">Dev\Grid\Model\ResourceModel\Category</argument>
5 </arguments>
6 </virtualType>

The collection class translates to app/code/Dev/Grid/Ui/DataProvider/Category/Listing/Collection.php:

Copied to your clipboard
1<?php
2/**
3 * Copyright © Magento, Inc. All rights reserved.
4 * See COPYING.txt for license details.
5 */
6
7namespace Dev\Grid\Ui\DataProvider\Category\Listing;
8
9use Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult;
10
11class Collection extends SearchResult
12{
13 /**
14 * Override _initSelect to add custom columns
15 *
16 * @return void
17 */
18 protected function _initSelect()
19 {
20 $this->addFilterToMap('entity_id', 'main_table.entity_id');
21 $this->addFilterToMap('name', 'devgridname.value');
22 parent::_initSelect();
23 }
24}

It uses a custom collection file to add custom filters to map, and makes the grid filters work with the ID and name fields. Without addFilterToMap, you will not be able to search within the name column.

The resource model class translates to app/code/Dev/Grid/Model/ResourceModel/Category.php:

Copied to your clipboard
1<?php
2/**
3 * Copyright © Magento, Inc. All rights reserved.
4 * See COPYING.txt for license details.
5 */
6
7namespace Dev\Grid\Model\ResourceModel;
8
9use Magento\Catalog\Model\ResourceModel\Category as BaseCategory;
10
11class Category extends BaseCategory
12{
13}

5. Column actions class#

The UI grid file defines a column actions class Dev\Grid\Ui\Component\Category\Listing\Column\Actions.

app/code/Dev/Grid/Ui/Component/Category/Listing/Column/Actions.php:

Copied to your clipboard
1<?php
2/**
3 * Copyright © Magento, Inc. All rights reserved.
4 * See COPYING.txt for license details.
5 */
6
7namespace Dev\Grid\Ui\Component\Category\Listing\Column;
8
9use Magento\Framework\View\Element\UiComponentFactory;
10use Magento\Framework\View\Element\UiComponent\ContextInterface;
11use Magento\Framework\Url;
12use Magento\Ui\Component\Listing\Columns\Column;
13
14class Actions extends Column
15{
16 /**
17 * @var UrlInterface
18 */
19 protected $_urlBuilder;
20
21 /**
22 * @var string
23 */
24 protected $_viewUrl;
25
26 /**
27 * Constructor
28 *
29 * @param ContextInterface $context
30 * @param UiComponentFactory $uiComponentFactory
31 * @param Url $urlBuilder
32 * @param string $viewUrl
33 * @param array $components
34 * @param array $data
35 */
36 public function __construct(
37 ContextInterface $context,
38 UiComponentFactory $uiComponentFactory,
39 Url $urlBuilder,
40 $viewUrl = '',
41 array $components = [],
42 array $data = []
43 ) {
44 $this->_urlBuilder = $urlBuilder;
45 $this->_viewUrl = $viewUrl;
46 parent::__construct($context, $uiComponentFactory, $components, $data);
47 }
48
49 /**
50 * Prepare Data Source
51 *
52 * @param array $dataSource
53 * @return array
54 */
55 public function prepareDataSource(array $dataSource)
56 {
57 if (isset($dataSource['data']['items'])) {
58 foreach ($dataSource['data']['items'] as &$item) {
59 $name = $this->getData('name');
60 if (isset($item['entity_id'])) {
61 $item[$name]['view'] = [
62 'href' => $this->_urlBuilder->getUrl($this->_viewUrl, ['id' => $item['entity_id']]),
63 'target' => '_blank',
64 'label' => __('View on Frontend')
65 ];
66 }
67 }
68 }
69 return $dataSource;
70 }
71}

It gets a frontend URL for every category it lists.

6. Backend controllers#

The main route defined in app/code/Dev/Grid/etc/adminhtml/menu.xml as dev_grid/index/index translates to app/code/Dev/Grid/Controller/Adminhtml/Index/Index.php:

Copied to your clipboard
1<?php
2/**
3 * Copyright © Magento, Inc. All rights reserved.
4 * See COPYING.txt for license details.
5 */
6
7namespace Dev\Grid\Controller\Adminhtml\Index;
8
9use Magento\Backend\App\Action;
10use Magento\Backend\App\Action\Context;
11use Magento\Framework\View\Result\Page;
12use Magento\Framework\View\Result\PageFactory;
13use Magento\Framework\App\Action\HttpGetActionInterface;
14
15class Index extends Action implements HttpGetActionInterface
16{
17 /**
18 * @var PageFactory
19 */
20 private $pageFactory;
21
22 /**
23 * Constructor
24 *
25 * @param Context $context
26 * @param PageFactory $rawFactory
27 */
28 public function __construct(
29 Context $context,
30 PageFactory $rawFactory
31 ) {
32 $this->pageFactory = $rawFactory;
33
34 parent::__construct($context);
35 }
36
37 /**
38 * Add the main Admin Grid page
39 *
40 * @return Page
41 */
42 public function execute(): Page
43 {
44 $resultPage = $this->pageFactory->create();
45 $resultPage->setActiveMenu('Magento_Catalog::catalog_products');
46 $resultPage->getConfig()->getTitle()->prepend(__('Admin Grid Tutorial Example'));
47
48 return $resultPage;
49 }
50}

The Ui grid file defines the custom route dev_grid/category/massDelete (mass delete) and translates to app/code/Dev/Grid/Controller/Adminhtml/Category/MassDelete.php:

Copied to your clipboard
1<?php
2/**
3 * Copyright © Magento, Inc. All rights reserved.
4 * See COPYING.txt for license details.
5 */
6
7namespace Dev\Grid\Controller\Adminhtml\Category;
8
9use Magento\Backend\App\Action;
10use Magento\Backend\App\Action\Context;
11use Magento\Backend\Model\View\Result\Redirect;
12use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory;
13use Magento\Catalog\Api\CategoryRepositoryInterface;
14use Magento\Framework\App\Action\HttpPostActionInterface;
15use Magento\Framework\Controller\ResultFactory;
16use Magento\Framework\Exception\NotFoundException;
17use Magento\Ui\Component\MassAction\Filter;
18
19class MassDelete extends Action implements HttpPostActionInterface
20{
21 /**
22 * Authorization level
23 */
24 const ADMIN_RESOURCE = 'Magento_Catalog::categories';
25
26 /**
27 * @var CollectionFactory
28 */
29 protected $collectionFactory;
30
31 /**
32 * @var CategoryRepositoryInterface
33 */
34 private $categoryRepository;
35
36 /**
37 * @var Filter
38 */
39 protected $filter;
40
41 /**
42 * Constructor
43 *
44 * @param Context $context
45 * @param Filter $filter
46 * @param CollectionFactory $collectionFactory
47 * @param CategoryRepositoryInterface $categoryRepository
48 */
49 public function __construct(
50 Context $context,
51 Filter $filter,
52 CollectionFactory $collectionFactory,
53 CategoryRepositoryInterface $categoryRepository
54 ) {
55 $this->filter = $filter;
56 $this->collectionFactory = $collectionFactory;
57 $this->categoryRepository = $categoryRepository;
58 parent::__construct($context);
59 }
60
61 /**
62 * Category delete action
63 *
64 * @return Redirect
65 */
66 public function execute(): Redirect
67 {
68 if (!$this->getRequest()->isPost()) {
69 throw new NotFoundException(__('Page not found'));
70 }
71 $collection = $this->filter->getCollection($this->collectionFactory->create());
72 $categoryDeleted = 0;
73 foreach ($collection->getItems() as $category) {
74 $this->categoryRepository->delete($category);
75 $categoryDeleted++;
76 }
77
78 if ($categoryDeleted) {
79 $this->messageManager->addSuccessMessage(
80 __('A total of %1 record(s) have been deleted.', $categoryDeleted)
81 );
82 }
83 return $this->resultFactory->create(ResultFactory::TYPE_REDIRECT)->setPath('dev_grid/index/index');
84 }
85}

Completed extension#

The complete extension can be found on GitHub at Admin Grid Example Extension. Installation instructions:

  1. Clone the repository

    Copied to your clipboard
    git clone https://github.com/goivvy/admin-grid-tutorial.git
  2. Copy app folder

    Copied to your clipboard
    cp -r ~/admin-grid/tutorial/app /path/to/magento2/root/folder
  3. Install and recompile

    Copied to your clipboard
    1bin/magento setup:upgrade
    2bin/magento deploy:mode:set production

The grid can now be accessed at Catalog > Inventory > Category Listing.

Was this helpful?
  • Privacy
  • Terms of Use
  • Do not sell my personal information
  • AdChoices
Copyright © 2022 Adobe. All rights reserved.