How to Add Custom Grid into a Custom Tab in Product Edit Page in Magento 2 Admin

If you have been on this blog before, you probably read the post Create Custom Tab under Product Edit Section. In that post, you have seen how we have added an HTML to custom tab without creating a product attribute. After reading that post many readers have requested to write a post on How to Add Custom Grid into a Custom Tab in Product Edit Page in Magento 2 Admin.

Today, we will create a custom tab in product edit page and in that tab we will display a custom grid from another module.

Add custom grid into a Custom Tab in Product edit page
Custom Grid In Product Edit Page

Before we start Module development

  • I assume that you have already created custom grid module. We do not cover how to create a custom grid module in Magento 2 admin in this post.
  • Your Magento 2 cache is disabled.

Getting Started: Development

Step 1: Create module.xml file under the app/code/Codextblog/Customgridtab/etc directory

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/internal/Magento/Framework/Module/etc/module.xsd">
    <module name="Codextblog_Customgridtab" setup_version="1.0.0"></module>
</config>

Step 2: Create registration.php file under the app/code/Codextblog/Customgridtab directory

<?php
 
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Codextblog_Customgridtab',
    __DIR__
);

Step 3: Declare modifier in app/code/Codextblog/Customgridtab/etc/adminhtml/di.xml file

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
   <virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool">
        <arguments>
            <argument name="modifiers" xsi:type="array">
                <item name="custom-tab-grid-with-content" xsi:type="array">
                    <item name="class" xsi:type="string">Codextblog\Customgridtab\Ui\DataProvider\Product\Form\Modifier\CustomTab</item>
                    <item name="sortOrder" xsi:type="number">10</item>
                </item>
            </argument>
        </arguments>
    </virtualType>
</config>

In this file Under the <item> tag, we have passed a class file. This file will create a custom tab in product edit page.

Step 4: Create modifier class CustomTab under app/code/Codextblog/Customgridtab/Ui/Dataprovider/Product/Form/Modifier directory

<?php namespace Codextblog\Customgridtab\Ui\DataProvider\Product\Form\Modifier; use Magento\Catalog\Model\Locator\LocatorInterface; use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier; use Magento\Framework\Stdlib\ArrayManager; use Magento\Framework\UrlInterface; use Magento\Ui\Component\Container; use Magento\Ui\Component\Form\Fieldset; class CustomTab extends AbstractModifier { const SAMPLE_FIELDSET_NAME = 'custom_grid_fieldset'; const SAMPLE_FIELD_NAME = 'custom_grid'; protected $_backendUrl; protected $_productloader; protected $_modelCustomgridFactory; /** * @var \Magento\Catalog\Model\Locator\LocatorInterface */ protected $locator; /** * @var ArrayManager */ protected $arrayManager; /** * @var UrlInterface */ protected $urlBuilder; /** * @var array */ protected $meta = []; /** * @param LocatorInterface $locator * @param ArrayManager $arrayManager * @param UrlInterface $urlBuilder */ public function __construct( LocatorInterface $locator, ArrayManager $arrayManager, UrlInterface $urlBuilder, \Magento\Catalog\Model\ProductFactory $_productloader, \Magento\Backend\Model\UrlInterface $backendUrl ) { $this->locator = $locator;
        $this->arrayManager = $arrayManager;
        $this->urlBuilder = $urlBuilder;
        $this->_productloader = $_productloader;
        $this->_backendUrl = $backendUrl;
    }
 
    public function modifyData(array $data)
    {
        return $data;
    }
 
    public function modifyMeta(array $meta)
    {
        $this->meta = $meta;
        $this->addCustomTab();
 
        return $this->meta;
    }
 
    protected function addCustomTab()
    {
        $this->meta = array_merge_recursive(
            $this->meta,
            [
                static::SAMPLE_FIELDSET_NAME => $this->getTabConfig(),
            ]
        );
    }
 
    protected function getTabConfig()
    {
    return [
        'arguments' => [
            'data' => [
                'config' => [
                    'label' => __('Custom Grid Tab'),
                    'componentType' => Fieldset::NAME,
                    'dataScope' => '',
                    'provider' => static::FORM_NAME . '.product_form_data_source',
                    'ns' => static::FORM_NAME,
                    'collapsible' => true,
                ],
            ],
        ],
        'children' => [
            static::SAMPLE_FIELD_NAME => [
                'arguments' => [
                    'data' => [
                        'config' => [
                            'autoRender' => true,
                            'componentType' => 'insertListing',
                            'dataScope' => 'custom_grid_listing',
                            'externalProvider' => 'custom_grid_listing.custom_grid_listing_data_source',
                            'selectionsProvider' => 'custom_grid_listing.custom_grid_listing.product_columns.ids',
                            'ns' => 'custom_grid_listing',
                            'render_url' => $this->urlBuilder->getUrl('mui/index/render'),
                            'realTimeLink' => false,
                            'behaviourType' => 'simple',
                            'externalFilterMode' => true,
                            'imports' => [
                                'productId' => '${ $.provider }:data.product.current_product_id'
                            ],
                            'exports' => [
                                'productId' => '${ $.externalProvider }:params.current_product_id'
                            ],

                        ],
                    ],
                ],
                'children' => [],
            ],
        ],
    ];
    }

}

This file is responsible for creating a custom tab. Function getTabConfig return an array that contains all the information which are needed to create a custom tab. In this function in line no 106 we have defined node ‘componentType’ with value as ‘insertlisting’ that means our tab will display the listing. In the previous post, we have defined the ‘componentType’ as a field because we need only filed to display but here we need to display grid so we have passed a value as ‘insertlisting’.

In line no 107, we have passed node ‘dataScope’ with value as ‘custom_grid_listing’ indicates the name of our ui_component element. Next we will define ui_component file custom_grid_listing.xml

Step 5: Create ui component xml file custom_grid_listing.xml under app/code/Codextblog/Customgridtab/view/adminhtml/ui_component directory

<?xml version="1.0" encoding="UTF-8"?>
<!-- /** * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -->
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <argument name="data" xsi:type="array">
        <item name="js_config" xsi:type="array">
            <item name="provider" xsi:type="string">custom_grid_listing.custom_grid_listing_data_source</item>
            <item name="deps" xsi:type="string">custom_grid_listing.custom_grid_listing_data_source</item>
        </item>
        <item name="spinner" xsi:type="string">custom_grid_columns</item>
    </argument>
    <dataSource name="custom_grid_listing_data_source">
        <argument name="dataProvider" xsi:type="configurableObject">
            <argument name="class" xsi:type="string">Codextblog\Customgridtab\Ui\DataProvider\Product\CustomDataProvider</argument>
            <argument name="name" xsi:type="string">custom_grid_listing_data_source</argument>
            <argument name="primaryFieldName" xsi:type="string">id</argument>
            <argument name="requestFieldName" xsi:type="string">id</argument>
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
                    <item name="update_url" xsi:type="url" path="mui/index/render"/>
                    <item name="storageConfig" xsi:type="array">
                        <item name="cacheRequests" xsi:type="boolean">false</item>
                    </item>
                </item>
            </argument>
        </argument>
    </dataSource>
    <listingToolbar name="listing_top">
        <filters name="listing_filters">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="statefull" xsi:type="array">
                        <item name="applied" xsi:type="boolean">false</item>
                    </item>
                    <item name="params" xsi:type="array">
                        <item name="filters_modifier" xsi:type="array"/>
                    </item>
                </item>
            </argument>
        </filters>
        <paging name="listing_paging"/>
    </listingToolbar>
    <columns name="custom_grid_columns" class="Magento\Ui\Component\Listing\Columns">
        <argument name="data" xsi:type="array">
            <item name="config" xsi:type="array">
                <item name="childDefaults" xsi:type="array">
                    <item name="fieldAction" xsi:type="array">
                        <item name="provider" xsi:type="string">customGrid</item>
                        <item name="target" xsi:type="string">selectData</item>
                        <item name="params" xsi:type="array">
                            <item name="0" xsi:type="string">${ $.$data.rowIndex }</item>
                        </item>
                    </item>
                </item>
            </item>
        </argument>
        <column name="id">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">textRange</item>
                    <item name="sorting" xsi:type="string">asc</item>
                    <item name="label" xsi:type="string" translate="true">ID</item>
                    <item name="sortOrder" xsi:type="number">0</item>
                </item>
            </argument>
        </column>
        <column name="title">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">text</item>
                    <item name="label" xsi:type="string" translate="true">Title</item>
                    <item name="sortOrder" xsi:type="number">10</item>
                    <item name="truncate" xsi:type="number">50</item>
                    <item name="nl2br" xsi:type="boolean">true</item>
                    <item name="escape" xsi:type="boolean">true</item>
                </item>
            </argument>
        </column>
        <column name="is_active" class="Codextblog\Customgridtab\Ui\Component\Listing\Columns\Active">
            <argument name="data" xsi:type="array">
                <item name="options" xsi:type="object">Codextblog\Customgridtab\Ui\Component\Listing\Columns\Active</item>
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">select</item>
                    <item name="dataType" xsi:type="string">select</item>
                    <item name="label" xsi:type="string" translate="true">Active</item>
                    <item name="sortOrder" xsi:type="number">20</item>
                </item>
            </argument>
        </column>
        <column name="date_created" class="Magento\Ui\Component\Listing\Columns\Date">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="filter" xsi:type="string">dateRange</item>
                    <item name="component" xsi:type="string">Magento_Ui/js/grid/columns/date</item>
                    <item name="dataType" xsi:type="string">date</item>
                    <item name="label" xsi:type="string" translate="true">Created At</item>
                    <item name="sortOrder" xsi:type="number">30</item>
                </item>
            </argument>
        </column>
    </columns>
</listing>

Let’s understand this file. The very important tag in this file is <dataSource> that contains the following arguments

  • class: define the DataProvider class Codextblog\Customgridtab\Ui\DataProvider\Product\CustomDataProvider which provide the collection for rendering the grid. We will create this class in next step.
  • name: define unique name
  • primaryFieldName: define the primary field of a database table which we use in our collection. In our case it is ‘id’.
  • requestFieldName: same as primaryFieldName
  • data: define some of the Magento 2 inbuilt data parameter to render the grid

Here other important tags are <listingtoolbar> and <columns> which are responsible for render toolbar (like pagination, filter) and display defined columns in the grid respectively. In the <columns> we have defined id, title, is_active and date_created so our grid will display only these four columns in the grid. We can add as many columns as we want. We just need to defined columns name with type under <columns> tag.

In one of the columns, we have defined is_active field. This field will render the drop-down with options. To render those options we have provided the class Codextblog\Customgridtab\Ui\Component\Listing\Columns\Active

step 6: Let’s create this class Active.php under app/code/Codextblog/Customgridtab/Ui/Component/Listing/Columns directory

<?php /** * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Codextblog\Customgridtab\Ui\Component\Listing\Columns; use Magento\Framework\Data\OptionSourceInterface; use Magento\Framework\View\Element\UiComponentFactory; use Magento\Ui\Component\Listing\Columns\Column; use Magento\Framework\View\Element\UiComponent\ContextInterface; /** * Class Active */ class Active extends Column implements OptionSourceInterface { /** * @param ContextInterface $context * @param UiComponentFactory $uiComponentFactory * @param StatusSource $source * @param array $components * @param array $data */ public function __construct( ContextInterface $context, UiComponentFactory $uiComponentFactory, array $components = [], array $data = [] ) { parent::__construct($context, $uiComponentFactory, $components, $data); } /** * {@inheritdoc} */ public function prepareDataSource(array $dataSource) { $dataSource = parent::prepareDataSource($dataSource); $options = [ 1 => __('Yes'),
        2 => __('No')

    ];

        if (empty($dataSource['data']['items'])) {
            return $dataSource;
        }

        foreach ($dataSource['data']['items'] as &$item) {
            if (isset($options[$item['is_active']])) {
                $item['is_active'] = $options[$item['is_active']];
            }
        }

        return $dataSource;
    }

    /**
     * {@inheritdoc}
     */
    public function toOptionArray()
    {
        $result = [];
        $result[] = ['value' => 1, 'label' => 'Yes'];
        $result[] = ['value' => 2, 'label' => 'No'];

        return $result;
    }


}

Step 7: Create Dataprovider class CustomDataProvider.php under app/code/Codextblog/Customgridtab/Ui/DataProvider/Product directory

<?php /** * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Codextblog\Customgridtab\Ui\DataProvider\Product; use Magento\Framework\App\RequestInterface; use Magento\Ui\DataProvider\AbstractDataProvider; use Codextblog\Customgrid\Model\ResourceModel\Post\CollectionFactory; use Codextblog\Customgrid\Model\ResourceModel\Post\Collection; use Codextblog\Customgrid\Model\Post; /** * Class CustomDataProvider * * @method Collection getCollection */ class CustomDataProvider extends AbstractDataProvider { /** * @var CollectionFactory */ protected $collectionFactory; /** * @var RequestInterface */ protected $request; /** * @param string $name * @param string $primaryFieldName * @param string $requestFieldName * @param CollectionFactory $collectionFactory * @param RequestInterface $request * @param array $meta * @param array $data */ public function __construct( $name, $primaryFieldName, $requestFieldName, CollectionFactory $collectionFactory, RequestInterface $request, array $meta = [], array $data = [] ) { parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data); $this->collectionFactory = $collectionFactory;
        $this->collection = $this->collectionFactory->create();
        $this->request = $request;
    }

    /**
     * {@inheritdoc}
     */
    public function getData()
    {
        $this->getCollection();

        $arrItems = [
            'totalRecords' => $this->getCollection()->getSize(),
            'items' => [],
        ];

        foreach ($this->getCollection() as $item) {
            $arrItems['items'][] = $item->toArray([]);
        }

        return $arrItems;
    }

    /**
     * {@inheritdoc}
     */
    public function addFilter(\Magento\Framework\Api\Filter $filter)
    {
        $field = $filter->getField();

        if (in_array($field, ['id','title','created_at', 'is_active'])) {
            $filter->setField($field);
        }



        parent::addFilter($filter);
    }
}

In this file you can see, We have added the Codextblog\Customgrid\Model\ResourceModel\Post\CollectionFactory as a dependency in the constructor. This is the CollectionFactory class of another module called Customgrid. This module grid we will render in our custom tab. Here function getData provides the data array which Magento 2 use to render the grid data. Function addFilter is responsible for providing filter data whenever we apply a filter in the grid.

That’s it. After following all the steps, run following commands and check your Magento 2 admin product edit page. You will see the “Custom Grid Tab” that render the grid.

php bin/magento setup:upgrade

php bin/magento setup:di:compile

php bin/magento setup:static-content:deploy

php bin/magento c:c

If you liked this post, then please like us on Facebook and follow us on Twitter.

Want to ask a question or leave a comment?

Leave a Comment

(28 Comments)

  • Asma Hawari

    thank you for your amazing efforts, however, I’m facing an issue while saving the selected values from the grid, do you have any idea on how to manage to save the values ? or any idea how can i catch the selected values by the admin ?
    Thank you in advanced

  • Mick

    how can i filter this by current product id

  • Dharmender

    Hi I have added custom grid in product edit but not able to filter by grid data with product id, can you please explain how filter product edit grid data with product id.

  • vipul

    i want to Custom Tab in Custom Model Edit Page in admin.please help me

  • Pedro

    Hi,
    Was following your example, but steps 4, 6 and 7 have incomplete code.
    Best regards

  • Dipesh

    Did u acheive grid serializer

    • Admin

      Not yet. I think grid serialise is not possible as same as Magento 1. You need to think other way like related products implementation.

  • Vishal

    i am getting error “Attention Something went wrong ” and after having checked with exception logs it says

    Source class \”\\Codextblog\\Customgrid\\Model\\ResourceModel\\Post\\Collection\” for \”Codextblog\\Customgrid\\Model\\ResourceModel\\Post\\CollectionFactory\” generation does not exist.

    so I realize the dependency injection you used from Codextblog\\Customgrid is not being compensated over here, can you guide what shall i do?

    • Admin

      Have you run code generator command after implementing this code? I think code is not generated in generation directory.

      • Vishal

        You are right, there is nothing related to this module in a generated folder, but I don’t get it, I removed generated and removed module entry from DB and reinstalled it, and followed each command provided by you but still no generation.
        this Codextblog\Customgrid is a different module than the one here, I think this module causing issue, can you please provide this module?

  • Raj

    hello sir

    i am getting error “Attention Something went wrong ”

    what would be the issue?

    • Admin

      Please check the error in ajax request from chrome dev tools under the network tab.

  • senthil

    I need to filter the custom grid by product id, so that I am trying to get the product id from the URL by $this->getRequest()->getParam(‘id’, false), but I am always getting the empty result, I also included the Http request in the constructor method and tried to get the current product id from the registry, but there is no luck.
    How to get the product id from the custom grid of admin

    • Kanimozhi D

      Hi iam facing same problem how to load grid filter by product id

  • Senthil

    Yes working awesome

    • Kanimozhi D

      Hi how load grid filter by product id I am facing same problem

    • Mukesh

      How you are able to get “current product id” in getData function of your DataProvider file please share how can we load the current product id.

      • Chirag

        Try this

        $this->request->getParam('id');
        
        • raj

          I tried this but i am getting NULL value.

  • Amrit

    How can we add a button here to open a modal?

  • Fudu

    My page is keep loading but no render the grid or tab after i follow your post ..

    • Chirag Dodia

      Please check the request URL, you will find an error that causes this issue.

  • Vishal

    HI Chirag Dodia,

    Did u achieve grid serializer .
    Even i am stuck with it..

    Thanks

    • Chirag Dodia

      Not yet. Will write a post once succeed with grid serialize.

  • hiren patelq

    please help

  • hiren patelq

    grid is showing properly but what if i have to add checkbox selection option and that selection value need to store in database

    • Chirag Dodia

      Hello Hiren,

      To display checkbox selection and save the value in the database is a part of grid serialization that is not within the scope of this post. Will check how we can achieve that. If I succeed I will write a separate post on it.

    • Vishal

      Did u acheive grid serializer

  • All the comments are goes into moderation before approval. Irrelevant comment with links directly goes to spam. Your email address will not be published.

    Was this post helpful? Please support Us!

    Follow us on twitter or Like us on facebook.

     


    To Avoid Spam Downloads, We Want Your Email

    We will send you download link right
    away. Please submit form below.
    SEND ME DOWNLOAD LINK
    Close

    Increase Your
    Magento 2
    Knowledge

    Get Weekly Tutorial
    to your Inbox
    Subscribe Me
    close-link
    Subscribe Here