How to create an admin grid in Magento 2?

Admin grids are used to present, filter, and sort various data in the Magento backend. Creating admin grids in Magento 2 is a complex process that requires knowledge of the code. 

Warning: all code snippets in this article are examples. You need to customize them according to your business needs.

Magento 2 admin grid tutorial

Magento 2 admin grid tutorial consists of several steps:

Step 1. First of all, you need to create a backbone module. As the namespace, we will use Dev_Grid:

mkdir-p app/code/Dev/Grid/etc

Then go to the app/code/Dev/Grid/etc/ folder and create the module.xml file with the following code:

<?xml version="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<modulename="Dev_Grid"setup_version="1.0.0">
  <sequence>
   <modulename="Magento_Backend"/>
   <modulename="Magento_Ui"/>
  </sequence>
</module>
</config>

Continue Magento 2 create admin grid process by registering a new module. Creating the app/code/Dev/Grid/registration.php file:

\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Dev_Grid',
    __DIR__
);

Next, add the routes.xml file to the app/code/Dev/Grid/etc/adminhtml/ folder. Use this code:

<?xml version="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <routerid="admin">
        <routeid="dev_grid"frontName="dev_grid">
            <modulename="Dev_Grid"before="Magento_Backend"/>
        </route>
    </router>
</config>

Finally, go to app/code/Dev/Grid/etc/adminhtml/ and create the menu.xml file:

<?xml version="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
    <menu>
        <addid="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"/>
    </menu>
</config>

Step 2. The next step of the Magento 2 create admin grid using layout process is to define the admin grid.

You can find a list of categories starting with the letter b or B in the grid. It will have three columns: ID, category path, and category name. ID and category path are taken from the catalog_category_entity table. Joins are used for the name values. Let’s create the page layout file app/code/Dev/Grid/view/adminhtml/layout/dev_grid_index_index.xml:

<?xml version="1.0"?>
<pagexmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainername="content">
            <uiComponentname="dev_grid_category_listing"/>
        </referenceContainer>
    </body>
</page>

The next step in Magento 2 ui component grid tutorial is needed to define the UI component dev_grid_category_listing. For this, create a separate file with the same name and .xml format in the - app/code/Dev/Grid/view/adminhtml/ui_component/dev_grid_category_listing.xml. You will need the following code:

<?xml version="1.0" encoding="UTF-8"?>
<listingxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
  <argumentname="data"xsi:type="array">
     <itemname="js_config"xsi:type="array">
        <itemname="provider"xsi:type="string">dev_grid_category_listing.dev_grid_category_listing_data_source</item>
        <itemname="deps"xsi:type="string">dev_grid_category_listing.dev_grid_category_listing_data_source</item>
     </item>
     <itemname="spinner"xsi:type="string">dev_grid_category_columns</item>
     <itemname="buttons"xsi:type="array">
        <itemname="add"xsi:type="array">
           <itemname="name"xsi:type="string">add</item>
           <itemname="label"xsi:type="string">View Category Tree</item>
           <itemname="class"xsi:type="string">primary</item>
           <itemname="url"xsi:type="string">catalog/category/index</item>
        </item>
     </item>
  </argument>
  <dataSourcename="dev_grid_category_listing_data_source">
   <argumentname="dataProvider"xsi:type="configurableObject">
       <argumentname="class"xsi:type="string">Dev\Grid\Ui\DataProvider\Category\ListingDataProvider</argument>
       <argumentname="name"xsi:type="string">dev_grid_category_listing_data_source</argument>
       <argumentname="primaryFieldName"xsi:type="string">entity_id</argument>
       <argumentname="requestFieldName"xsi:type="string">entity_id</argument>
       <argumentname="data"xsi:type="array">
         <itemname="config"xsi:type="array">
           <itemname="update_url"xsi:type="url"path="mui/index/render"/>
           <itemname="storageConfig"xsi:type="array">
             <itemname="indexField"xsi:type="string">entity_id</item>
           </item>
         </item>
       </argument>
   </argument>
   <argumentname="data"xsi:type="array">
     <itemname="js_config"xsi:type="array">
        <itemname="component"xsi:type="string">Magento_Ui/js/grid/provider</item>
     </item>
   </argument>
  </dataSource>
  <listingToolbarname="listing_top">
    <bookmarkname="bookmarks"/>
    <columnsControlsname="columns_controls"/>
    <massactionname="listing_massaction">
      <argumentname="data"xsi:type="array">
        <itemname="data"xsi:type="array">
           <itemname="selectProvider"xsi:type="string">dev_grid_category_listing.dev_grid_category_listing.dev_grid_category_columns.ids</item>
           <itemname="displayArea"xsi:type="string">bottom</item>
           <itemname="component"xsi:type="string">Magento_Ui/js/grid/tree-massactions</item>
           <itemname="indexField"xsi:type="string">entity_id</item>
        </item>
      </argument>
      <actionname="delete">
         <argumentname="data"xsi:type="array">
           <itemname="config"xsi:type="array">
               <itemname="type"xsi:type="string">delete</item>
               <itemname="label"xsi:type="string"translate="true">Delete</item>
               <itemname="url"xsi:type="url"path="dev_grid/category/massDelete"/>
               <itemname="confirm"xsi:type="array">
                  <itemname="title"xsi:type="string"translate="true">Delete items</item>
                  <itemname="message"xsi:type="string"translate="true">Are you sure you want to delete selected items?</item>
               </item>
           </item>
         </argument>
      </action>
    </massaction>
    <filtersname="listing_filters">
            <argumentname="data"xsi:type="array">
                <itemname="config"xsi:type="array">
                    <itemname="templates"xsi:type="array">
                        <itemname="filters"xsi:type="array">
                            <itemname="select"xsi:type="array">
                                <itemname="component"xsi:type="string">Magento_Ui/js/form/element/ui-select</item>
                                <itemname="template"xsi:type="string">ui/grid/filters/elements/ui-select</item>
                            </item>
                        </item>
                    </item>
                </item>
            </argument>
    </filters>
    <pagingname="listing_paging"/>
  </listingToolbar>
  <columnsname="dev_grid_category_columns">
    <selectionsColumnname="ids">
       <argumentname="data"xsi:type="array">
           <itemname="config"xsi:type="array">
              <itemname="indexField"xsi:type="string">entity_id</item>
           </item>
       </argument>
    </selectionsColumn>
    <columnname="entity_id">
      <settings>
         <filter>textRange</filter>
         <labeltranslate="true">ID</label>
         <resizeDefaultWidth>25</resizeDefaultWidth>
      </settings>
    </column>
    <columnname="path">
      <settings>
         <filter>text</filter>
         <bodyTmpl>ui/grid/cells/text</bodyTmpl>
         <labeltranslate="true">Path</label>
     </settings>
    </column>
    <columnname="name">
      <settings>
         <filter>text</filter>
         <bodyTmpl>ui/grid/cells/text</bodyTmpl>
         <labeltranslate="true">Name</label>
      </settings>
    </column>
    <columnname="created_at"class="Magento\Ui\Component\Listing\Columns\Date"component="Magento_Ui/js/grid/columns/date">
      <settings>
        <filter>dateRange</filter>
        <dataType>date</dataType>
        <labeltranslate="true">Created</label>
      </settings>
    </column>
    <actionsColumnname="actions"class="Dev\Grid\Ui\Component\Category\Listing\Column\Actions"sortOrder="200">
       <argumentname="data"xsi:type="array">
          <itemname="config"xsi:type="array">
              <itemname="resizeEnabled"xsi:type="boolean">false</item>
              <itemname="resizeDefaultWidth"xsi:type="string">107</item>
              <itemname="indexField"xsi:type="string">entity_id</item>
          </item>
       </argument>
       <argumentname="viewUrl"xsi:type="string">catalog/category/view</argument>
    </actionsColumn>
  </columns>
</listing>

This file consist of the following sections:

  • DataSource refers to the class responsible for getting the requested data.
  • Mass actions and filters are defined in the listingToolbar section.
  • The columns section lists the displayed columns.

Step 3. The next step is defining the DataSource Class:

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

You need to create the app/code/Dev/Grid/Ui/DataProvider/Category/ListingDataProvider.php file with the following code:

namespaceDev\Grid\Ui\DataProvider\Category;

classListingDataProviderextends\Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider

{

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

app/code/Dev/Grid/etc/di.xml. Here you need to use the next code:

<?xml version="1.0"?>
<configxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<typename="Dev\Grid\Ui\DataProvider\Category\ListingDataProvider">
   <pluginname="dev_grid_attributes"type="Dev\Grid\Plugin\AddAttributesToUiDataProvider"/>
</type>
<typename="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
  <arguments>
   <argumentname="collections"xsi:type="array">
     <itemname="dev_grid_category_listing_data_source"xsi:type="string">DevGridCategoryCollection</item>
   </argument>
  </arguments>
</type>
<virtualTypename="DevGridCategoryCollection"type="Dev\Grid\Ui\DataProvider\Category\Listing\Collection">
   <arguments>
     <argumentname="mainTable"xsi:type="string">catalog_category_entity</argument>
     <argumentname="resourceModel"xsi:type="string">Dev\Grid\Model\ResourceModel\Category</argument>
   </arguments>
</virtualType>
</config>

Now you need to go to app/code/Dev/Grid/Plugin/AddAttributesToUiDataProvider.php and use this code:

namespaceDev\Grid\Plugin;
useDev\Grid\Ui\DataProvider\Category\ListingDataProviderasCategoryDataProvider;
useMagento\Eav\Api\AttributeRepositoryInterface;
useMagento\Framework\App\ProductMetadataInterface;
useMagento\Framework\View\Element\UiComponent\DataProvider\SearchResult;
classAddAttributesToUiDataProvider
{
    /** @var AttributeRepositoryInterface */
    private$attributeRepository;
    /** @var ProductMetadataInterface */
    private$productMetadata;
    /**
     * Constructor
     *
     * @param \Magento\Eav\Api\AttributeRepositoryInterface $attributeRepository
     * @param \Magento\Framework\App\ProductMetadataInterface $productMetadata
     */
    publicfunction__construct(
        AttributeRepositoryInterface$attributeRepository,
        ProductMetadataInterface$productMetadata
    ){
        $this->attributeRepository=$attributeRepository;
        $this->productMetadata=$productMetadata;
    }
    /**
     * Get Search Result after plugin
     *
     * @param \Dev\Grid\Ui\DataProvider\Category\ListingDataProvider $subject
     * @param \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult $result
     * @return \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult
     */
    publicfunctionafterGetSearchResult(CategoryDataProvider$subject,SearchResult$result)
    {
        if($result->isLoaded()){
            return$result;
        }
        $edition=$this->productMetadata->getEdition();
        $column='entity_id';
        if($edition=='Enterprise'){
            $column='row_id';
        }
        $attribute=$this->attributeRepository->get('catalog_category','name');
        $result->getSelect()->joinLeft(
            ['devgridname'=>$attribute->getBackendTable()],
            'devgridname.'.$column.' = main_table.'.$column.' AND devgridname.attribute_id = '
            .$attribute->getAttributeId(),
            ['name'=>'devgridname.value']
        );
        $result->getSelect()->where('devgridname.value LIKE "B%"');
        return$result;
    }
}

It will work with both enterprise and community versions by linking to different fields. LIKE will be case insensitive in this case.

Step 4. The next step is a 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 file.

The main table and resource mode are set by the di.xml file. Use the following code:

<virtualTypename="DevGridCategoryCollection"type="Dev\Grid\Ui\DataProvider\Category\Listing\Collection">
   <arguments>
     <argumentname="mainTable"xsi:type="string">catalog_category_entity</argument>
     <argumentname="resourceModel"xsi:type="string">Dev\Grid\Model\ResourceModel\Category</argument>
   </arguments>
</virtualType>

The collection class will transform into app/code/Dev/Grid/Ui/DataProvider/Category/Listing/Collection.php. Use this code:

namespaceDev\Grid\Ui\DataProvider\Category\Listing;

useMagento\Framework\View\Element\UiComponent\DataProvider\SearchResult;
 
classCollectionextendsSearchResult
{
    /**
     * Override _initSelect to add custom columns
     *
     * @return void
     */
    protectedfunction_initSelect()
    {
        $this->addFilterToMap('entity_id','main_table.entity_id');
        $this->addFilterToMap('name','devgridname.value');
        parent::_initSelect();
    }
}

To make grid filters work with ID and name fields, it implements a custom collection file to add custom filters to the map. You can not search within the column name without the addFilterToMap command.

Step 5. The next step is to define the column actions class.

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

Then you need to go to the app/code/Dev/Grid/Ui/Component/Category/Listing/Column/Actions.php and use the following code:

namespaceDev\Grid\Ui\Component\Category\Listing\Column;
 
useMagento\Framework\View\Element\UiComponentFactory;
useMagento\Framework\View\Element\UiComponent\ContextInterface;
useMagento\Framework\Url;
useMagento\Ui\Component\Listing\Columns\Column;
 
classActionsextendsColumn
{
    /**
     * @var UrlInterface
     */
    protected$_urlBuilder;
 
    /**
     * @var string
     */
    protected$_viewUrl;
 
    /**
     * Constructor
     *
     * @param \Magento\Framework\View\Element\UiComponent\ContextInterface $context
     * @param \Magento\Framework\View\Element\UiComponentFactory $uiComponentFactory
     * @param \Magento\Framework\Url $urlBuilder
     * @param string $viewUrl
     * @param array $components
     * @param array $data
     */
    publicfunction__construct(
        ContextInterface$context,
        UiComponentFactory$uiComponentFactory,
        Url$urlBuilder,
        $viewUrl='',
        array$components=[],
        array$data=[]
    ){
        $this->_urlBuilder=$urlBuilder;
        $this->_viewUrl    =$viewUrl;
        parent::__construct($context,$uiComponentFactory,$components,$data);
    }
 
    /**
     * Prepare Data Source
     *
     * @param array $dataSource
     * @return array
     */
    publicfunctionprepareDataSource(array$dataSource)
    {
        if(isset($dataSource['data']['items'])){
            foreach($dataSource['data']['items']as&$item){
                $name=$this->getData('name');
                if(isset($item['entity_id'])){
                    $item[$name]['view']   =[
                        'href'=>$this->_urlBuilder->getUrl($this->_viewUrl,['id'=>$item['entity_id']]),
                        'target'=>'_blank',
                        'label'=>__('View on Frontend')
                    ];
                }
            }
        }
        return$dataSource;
    }
}
 

It gets the frontend URL for each category it lists. 

Step 6. The last step is about backend controllers.

As dev_grid/index/index translates into app/code/Dev/Grid/Controller/Adminhtml/Index/Index.php, the main route will be defined in app/code/Dev/Grid/etc/adminhtml/menu.xml. Now you need to use this code:

namespaceDev\Grid\Controller\Adminhtml\Index;
 
useMagento\Backend\App\Action;
useMagento\Backend\App\Action\Context;
useMagento\Framework\View\Result\Page;
useMagento\Framework\View\Result\PageFactory;
 
classIndexextendsActionimplementsHttpGetActionInterface
{
    /**
     * @var PageFactory
     */
    private$pageFactory;
 
    /**
     * Constructor
     *
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Framework\View\Result\PageFactory $rawFactory
     */
    publicfunction__construct(
        Context$context,
        PageFactory$rawFactory
    ){
        $this->pageFactory=$rawFactory;
 
        parent::__construct($context);
    }
 
    /**
     * Add the main Admin Grid page
     *
     * @return Page
     */
    publicfunctionexecute():Page
    {
        $resultPage=$this->pageFactory->create();
        $resultPage->setActiveMenu('Magento_Catalog::catalog_products');
        $resultPage->getConfig()->getTitle()->prepend(__('Admin Grid Tutorial Example'));
 
        return$resultPage;
    }
}

 

The UI grid file defines the custom dev_grid/category/massDelete route and transforms into app/code/Dev/Grid/Controller/Adminhtml/Category/MassDelete.php with the use of the following code:

namespaceDev\Grid\Controller\Adminhtml\Category;
 
useMagento\Backend\App\Action;
useMagento\Backend\App\Action\Context;
useMagento\Backend\Model\View\Result\Redirect;
useMagento\Catalog\Model\ResourceModel\Category\CollectionFactory;
useMagento\Catalog\Api\CategoryRepositoryInterface;
useMagento\Framework\App\Action\HttpPostActionInterface;
useMagento\Framework\Controller\ResultFactory;
useMagento\Framework\Exception\NotFoundException;
useMagento\Ui\Component\MassAction\Filter;
 
classMassDeleteextendsActionimplementsHttpPostActionInterface
{
    /**
     * Authorization level
     */
    constADMIN_RESOURCE='Magento_Catalog::categories';
 
    /**
     * @var \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory
     */
    protected$collectionFactory;
 
    /**
     * @var \Magento\Catalog\Api\CategoryRepositoryInterface
     */
    private$categoryRepository;
 
    /**
     * @var \Magento\Ui\Component\MassAction\Filter
     */
    protected$filter;
 
    /**
     * Constructor
     *
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Ui\Component\MassAction\Filter $filter
     * @param \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $collectionFactory
     * @param \Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository
     */
    publicfunction__construct(
        Context$context,
        Filter$filter,
        CollectionFactory$collectionFactory,
        CategoryRepositoryInterface$categoryRepository
    ){
        $this->filter=$filter;
        $this->collectionFactory=$collectionFactory;
        $this->categoryRepository=$categoryRepository;
        parent::__construct($context);
    }
 
    /**
     * Category delete action
     *
     * @return Redirect
     */
    publicfunctionexecute():Redirect
    {
        if(!$this->getRequest()->isPost()){
            thrownewNotFoundException(__('Page not found'));
        }
        $collection=$this->filter->getCollection($this->collectionFactory->create());
        $categoryDeleted=0;
        foreach($collection->getItems()as$category){
            $this->categoryRepository->delete($category);
            $categoryDeleted++;
        }
 
        if($categoryDeleted){
            $this->messageManager->addSuccessMessage(
                __('A total of %1 record(s) have been deleted.',$categoryDeleted)
            );
        }
        return$this->resultFactory->create(ResultFactory::TYPE_REDIRECT)->setPath('dev_grid/index/index');
    }
}

 

How to create an admin grid using the UI component in Magento 2?

Creating an admin grid using the UI component is quite a challenge. And if you have no desire to understand the code, you can pay attention to 3rd party plugins, for example, our Extended Product Grid.

To improve the functionality of an existing product table, you can use the Extended Product Grid with Editor extension. You will be provided with several functions: 

  • add additional columns to the grid with product attributes;
  • sort products by tailored parameters;
  • promptly change product data with AJAX;
  • create numerous product grid templates and others.

This extension will allow you to extend the product grid by adding custom columns, which are organized in groups. Customized grid views can be saved as templates, so you can use them any time you want.

How can we help you?

Didn’t you find the answer to your question? We are always happy to help you out.