Weβve noticed that many Magento 2 developers face difficulties when trying to create UI component grid and form in the admin panel.
In this tutorial, we will show you how to
- Create UI component grid and
- Create UI component form
- CRUD operations such as save, delete, update, massStatus, massDelete records
Here is the step-by-step process to create UI component grid and form from scratch in Magento 2.
1. Activate Module
Create The Module Configuration File Named Module.Xml
Create app/code/Thecoachsmb/Grid/etc/module.xml file
<?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="Thecoachsmb_Grid" setup_version="1.0.0">
</module>
</config>
Create Registration.php file for registering module
Create app/code/Thecoachsmb/Grid/registration.php file
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Thecoachsmb_Grid',
__DIR__
);
2.Β Create Route file routes.xml
Now, the next step is to add an admin router of the module.Β For that, we need to create aΒ routes.xml fileΒ In app/code/Thecoachsmb/Grid/etc/adminhtml/routes.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="admin">
<route id="thecoachsmb" frontName="thecoachsmb">
<module name="Thecoachsmb_Grid" before="Magento_Adminhtml"/>
</route>
</router>
</config>
3. Create Menu for our custom module in Admin Panel
Next, we will have to add link in admin menu for which we need to create menu.xml
Create app/code/Thecoachsmb/Grid/etc/adminhtml/menu.xml file
Menu Name – Thecoachsmb
Sub-Menu Name – Articles
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Backend:etc/menu.xsd">
<menu>
<add id="Thecoachsmb_Grid::gridmanager" title="Thecoachsmb" module="Thecoachsmb_Grid" sortOrder="10" resource="Thecoachsmb_Grid::gridmanager"/>
<add id="Thecoachsmb_Grid::create" title="Grid Manager" module="Thecoachsmb_Grid" sortOrder="20" parent="Thecoachsmb_Grid::gridmanager" action="thecoachsmb/grid/index" resource="Thecoachsmb_Grid::create"/>
</menu>
</config>
Clear cache : php bin/magento c:f
Refresh the Admin Panel, you should see Menu as shown below.

4. Create Table
Create app/code/Thecoachsmb/Grid/etc/db_schema.xml file
Here Table Name – thecoachsmb_article
Columns – article_id, title
<?xml version="1.0"?> <schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> <table name="thecoachsmb_article_grid" resource="default" engine="innodb" comment="Thecoachsmb Article"> <column xsi:type="int" name="article_id" unsigned="true" nullable="false" identity="true" comment="Article ID"/> <column xsi:type="varchar" name="title" nullable="true" length="255" comment="Title"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="article_id"/> </constraint> </table> </schema>
Now, run the below command to update this table in the databse:
php bin/magento setup:upgrade && php bin/magento setup:static-content:deploy -f
5. Create the Model file
After that, we will need to take care of the business logic and a single instance of object. So, letβs create Grid.php in Β app/code/Thecoachsmb/Grid/Model/Grid.php file
<?php
namespace Thecoachsmb\Grid\Model;
use Thecoachsmb\Grid\Api\Data\GridInterface;
class Grid extends \Magento\Framework\Model\AbstractModel implements GridInterface
{
/**
* CMS page cache tag.
*/
const CACHE_TAG = 'thecoachsmb_article';
/**
* @var string
*/
protected $_cacheTag = 'thecoachsmb_article';
/**
* Prefix of model events names.
*
* @var string
*/
protected $_eventPrefix = 'thecoachsmb_article';
/**
* Initialize resource model.
*/
protected function _construct()
{
$this->_init('Thecoachsmb\Grid\Model\ResourceModel\Grid');
}
/**
* Get EntityId.
*
* @return int
*/
public function getArticleId()
{
return $this->getData(self::ARTICLE_ID);
}
/**
* Set EntityId.
*/
public function setArticleId($articleId)
{
return $this->setData(self::ARTICLE_ID, $articleId);
}
/**
* Get Title.
*
* @return varchar
*/
public function getTitle()
{
return $this->getData(self::TITLE);
}
/**
* Set Title.
*/
public function setTitle($title)
{
return $this->setData(self::TITLE, $title);
}
}
6. Now, Create the InterfaceΒ file
Create GridInterface.php in app\code\Thecoachsmb\Grid\Api\Data folder.
<?php
namespace Thecoachsmb\Grid\Api\Data;
interface GridInterface
{
/**
* Constants for keys of data array. Identical to the name of the getter in snake case.
*/
const ARTICLE_ID = 'article_id';
const TITLE = 'title';
/**
* Get ArticleId.
*
* @return int
*/
public function getArticleId();
/**
* Set ArticleId.
*/
public function setArticleId($articleId);
/**
* Get Title.
*
* @return varchar
*/
public function getTitle();
/**
* Set Title.
*/
public function setTitle($title);
}
7. Create the Resource Model file
Now, for table βthecoachsmb_article_gridβ, create the ResourceModel File
<?php
namespace Thecoachsmb\Grid\Model\ResourceModel;
/**
* Grid Grid mysql resource.
*/
class Grid extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
/**
* @var string
*/
protected $_idFieldName = 'article_id';
/**
* @var \Magento\Framework\Stdlib\DateTime\DateTime
*/
protected $_date;
/**
* Construct.
*
* @param \Magento\Framework\Model\ResourceModel\Db\Context $context
* @param \Magento\Framework\Stdlib\DateTime\DateTime $date
* @param string|null $resourcePrefix
*/
public function __construct(
\Magento\Framework\Model\ResourceModel\Db\Context $context,
\Magento\Framework\Stdlib\DateTime\DateTime $date,
$resourcePrefix = null
)
{
parent::__construct($context, $resourcePrefix);
$this->_date = $date;
}
/**
* Initialize resource model.
*/
protected function _construct()
{
$this->_init('thecoachsmb_article_grid', 'article_id');
}
}
8. Create the Collection file Collection.php
In app/code/Thecoachsmb/Grid/Model/ResourceModel/Grid/Collection.php
<?php
namespace Thecoachsmb\Grid\Model\ResourceModel\Grid;
class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
{
/**
* @var string
*/
protected $_idFieldName = 'article_id';
/**
* Define resource model.
*/
protected function _construct()
{
$this->_init('Thecoachsmb\Grid\Model\Grid', 'Thecoachsmb\Grid\Model\ResourceModel\Grid');
}
}
9. Create Controller to display grid in the Menu – Grid Manager
Now, we will create controller file Index.php in app/code/Thecoachsmb/Grid/Controller/Adminhtml/Grid
<?php
namespace Thecoachsmb\Grid\Controller\Adminhtml\Grid;
class Index extends \Magento\Backend\App\Action
{
/**
* @var \Magento\Framework\View\Result\PageFactory
*/
protected $_resultPageFactory;
/**
* @param \Magento\Backend\App\Action\Context $context
* @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
*/
public function __construct(
\Magento\Backend\App\Action\Context $context,
\Magento\Framework\View\Result\PageFactory $resultPageFactory
)
{
parent::__construct($context);
$this->_resultPageFactory = $resultPageFactory;
}
/**
* Grid List page.
*
* @return \Magento\Backend\Model\View\Result\Page
*/
public function execute()
{
/** @var \Magento\Backend\Model\View\Result\Page $resultPage */
$resultPage = $this->_resultPageFactory->create();
$resultPage->setActiveMenu('Thecoachsmbl_Grid::grid_list');
$resultPage->getConfig()->getTitle()->prepend(__('Grid List'));
return $resultPage;
}
/**
* Check Grid List Permission.
*
* @return bool
*/
protected function _isAllowed()
{
return $this->_authorization->isAllowed('Thecoachsmb_Grid::grid_list');
}
}
10. Display Grid
Ceate layout file thecoachsmb_grid_index.xml in app/code/Thecoachsmb/Grid/view/adminhtml/layout
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<!-- here we call our ui component of grid-->
<uiComponent name="thecoachsmb_grid_listing"/>
</referenceContainer>
</body>
</page>
11. Create ui component for grid row list.
Weβll create thecoachsmb_grid_listing.xml in app/code/Thecoachsmb/Grid/view/adminhtml/ui_component folder as following.
<?xml version="1.0" encoding="UTF-8"?>
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Ui/etc/ui_configuration.xsd">
<argument name="data" xsi:type="array">
<item name="js_config" xsi:type="array">
<item name="provider" xsi:type="string">thecoachsmb_grid_listing.thecoachsmb_grid_listing_data_source</item>
<item name="deps" xsi:type="string">thecoachsmb_grid_listing.thecoachsmb_grid_listing_data_source</item>
</item>
<item name="spinner" xsi:type="string">thecoachsmb_records_columns</item>
<item name="buttons" xsi:type="array">
<item name="add" xsi:type="array">
<item name="name" xsi:type="string">add</item>
<item name="label" xsi:type="string" translate="true">Add New Article</item>
<item name="class" xsi:type="string">primary</item>
<item name="url" xsi:type="string">*/*/addrow</item>
</item>
</item>
</argument>
<dataSource name="thecoachsmb_grid_listing_data_source">
<argument name="dataProvider" xsi:type="configurableObject">
<argument name="class" xsi:type="string">Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider</argument>
<argument name="name" xsi:type="string">thecoachsmb_grid_listing_data_source</argument>
<argument name="primaryFieldName" xsi:type="string">article_id</argument>
<argument name="requestFieldName" xsi:type="string">id</argument>
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="update_url" xsi:type="url" path="mui/index/render"/>
</item>
</argument>
</argument>
<argument name="data" xsi:type="array">
<item name="js_config" xsi:type="array">
<item name="component" xsi:type="string">Magento_Ui/js/grid/provider</item>
</item>
</argument>
</dataSource>
<columns name="thecoachsmb_records_columns">
<selectionsColumn name="ids">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="indexField" xsi:type="string">article_id</item>
<item name="sorting" xsi:type="string">desc</item>
<item name="sortOrder" xsi:type="number">0</item>
</item>
</argument>
</selectionsColumn>
<column name="article_id" sortOrder="20">
<settings>
<filter>textRange</filter>
<label translate="true">ID</label>
<sorting>asc</sorting>
</settings>
</column>
<column name="title">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="filter" xsi:type="string">textRange</item>
<item name="label" xsi:type="string" translate="true">Title</item>
</item>
</argument>
</column>
<!-- Add Action with each row of grid and for this we will create a class Action -->
<actionsColumn name="actions" class="Thecoachsmb\Grid\Ui\Component\Listing\Grid\Column\Action">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="resizeEnabled" xsi:type="boolean">false</item>
<item name="resizeDefaultWidth" xsi:type="string">107</item>
<item name="indexField" xsi:type="string">id</item>
</item>
</argument>
</actionsColumn>
</columns>
</listing>
12. Create di.xml file for map data provider with collection
In app/code/Thecoachsmb/Grid/etc/di.xml
<?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="Thecoachsmb\Grid\Api\Data\GridInterface" type="Thecoachsmb\Grid\Model\Grid" />
<virtualType name="Thecoachsmb\Grid\Model\ResourceModel\Grid\Grid\Collection" type="Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult">
<arguments>
<argument name="mainTable" xsi:type="string">thecoachsmb_article_grid</argument>
<argument name="resourceModel" xsi:type="string">Thecoachsmb\Grid\Model\ResourceModel\Grid</argument>
</arguments>
</virtualType>
<type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
<arguments>
<argument name="collections" xsi:type="array">
<item name="thecoachsmb_grid_listing_data_source" xsi:type="string">Thecoachsmb\Grid\Model\ResourceModel\Grid\Grid\Collection</item>
</argument>
</arguments>
</type>
</config>
13. Create Action Class Action.php
In app/code/Thecoachsmb/Grid/Ui/Component/Listing/Grid/Column/Action.php
<?php
namespace Thecoachsmb\Grid\Ui\Component\Listing\Grid\Column;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Ui\Component\Listing\Columns\Column;
use Magento\Framework\UrlInterface;
class Action extends Column
{
/** Url path */
const ROW_EDIT_URL = 'thecoachsmb/grid/addrow';
/** @var UrlInterface */
protected $_urlBuilder;
/**
* @var string
*/
private $_editUrl;
/**
* @param ContextInterface $context
* @param UiComponentFactory $uiComponentFactory
* @param UrlInterface $urlBuilder
* @param array $components
* @param array $data
* @param string $editUrl
*/
public function __construct(
ContextInterface $context,
UiComponentFactory $uiComponentFactory,
UrlInterface $urlBuilder,
array $components = [],
array $data = [],
$editUrl = self::ROW_EDIT_URL
)
{
$this->_urlBuilder = $urlBuilder;
$this->_editUrl = $editUrl;
parent::__construct($context, $uiComponentFactory, $components, $data);
}
/**
* Prepare Data Source.
*
* @param array $dataSource
*
* @return array
*/
public function prepareDataSource(array $dataSource)
{
if (isset($dataSource['data']['items'])) {
foreach ($dataSource['data']['items'] as &$item) {
$name = $this->getData('name');
if (isset($item['article_id'])) {
$item[$name]['edit'] = [
'href' => $this->_urlBuilder->getUrl(
$this->_editUrl,
['id' => $item['article_id']]
),
'label' => __('Edit'),
];
}
}
}
return $dataSource;
}
}
14. Now weβll start how to save/edit data in table using form
Above in our Ui component file thecoachsmb_grid_listing.xml we already add path of add form url now weβll create controller file AddRow.php in app/code/Thecoachsmb/Grid/Controller/Adminhtml/Grid
<?php
namespace Thecoachsmb\Grid\Controller\Adminhtml\Grid;
use Magento\Framework\Controller\ResultFactory;
class AddRow extends \Magento\Backend\App\Action
{
/**
* @var \Magento\Framework\Registry
*/
private $coreRegistry;
/**
* @var \Thecoachsmb\Grid\Model\GridFactory
*/
private $gridFactory;
/**
* @param \Magento\Backend\App\Action\Context $context
* @param \Magento\Framework\Registry $coreRegistry,
* @param \Thecoachsmb\Grid\Model\GridFactory $gridFactory
*/
public function __construct(
\Magento\Backend\App\Action\Context $context,
\Magento\Framework\Registry $coreRegistry,
\Thecoachsmb\Grid\Model\GridFactory $gridFactory
) {
parent::__construct($context);
$this->coreRegistry = $coreRegistry;
$this->gridFactory = $gridFactory;
}
/**
* Mapped Grid List page.
* @return \Magento\Backend\Model\View\Result\Page
*/
public function execute()
{
$rowId = (int) $this->getRequest()->getParam('id');
$rowData = $this->gridFactory->create();
/** @var \Magento\Backend\Model\View\Result\Page $resultPage */
if ($rowId) {
$rowData = $rowData->load($rowId);
$rowTitle = $rowData->getTitle();
if (!$rowData->getArticleId()) {
$this->messageManager->addError(__('row data no longer exist.'));
$this->_redirect('thecoachsmb/grid/rowdata');
return;
}
}
$this->coreRegistry->register('row_data', $rowData);
$resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE);
$title = $rowId ? __('Edit Row Data ').$rowTitle : __('Add Row Data');
$resultPage->getConfig()->getTitle()->prepend($title);
return $resultPage;
}
protected function _isAllowed()
{
return $this->_authorization->isAllowed('Thecoachsmb_Grid::add_row');
}
}
15. Create layout file thecoachsmb_grid_addrow.xml to display the form
For render this form create layout file thecoachsmb_grid_addrow.xml in app/code/Thecoachsmb/Grid/view/adminhtml/layout
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="admin-1column"
xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="content">
<block class="Thecoachsmb\Grid\Block\Adminhtml\Grid\AddRow" name="add_row" />
</referenceContainer>
</body>
</page>
16. Add Insert Action
Create block file of form AddRow.php in app/code/Thecoachsmb/Grid/Block/Adminhtml/Grid
<?php
namespace Thecoachsmb\Grid\Block\Adminhtml\Grid;
class AddRow extends \Magento\Backend\Block\Widget\Form\Container
{
/**
* Core registry.
*
* @var \Magento\Framework\Registry
*/
protected $_coreRegistry = null;
/**
* @param \Magento\Backend\Block\Widget\Context $context
* @param \Magento\Framework\Registry $registry
* @param array $data
*/
public function __construct(
\Magento\Backend\Block\Widget\Context $context,
\Magento\Framework\Registry $registry,
array $data = []
)
{
$this->_coreRegistry = $registry;
parent::__construct($context, $data);
}
/**
* Initialize Imagegallery Images Edit Block.
*/
protected function _construct()
{
$this->_objectId = 'row_id';
$this->_blockGroup = 'Thecoachsmb_Grid';
$this->_controller = 'adminhtml_grid';
parent::_construct();
if ($this->_isAllowedAction('Thecoachsmb_Grid::add_row')) {
$this->buttonList->update('save', 'label', __('Save'));
} else {
$this->buttonList->remove('save');
}
$this->buttonList->remove('reset');
}
/**
* Retrieve text for header element depending on loaded image.
*
* @return \Magento\Framework\Phrase
*/
public function getHeaderText()
{
return __('Add Article Data');
}
/**
* Check permission for passed action.
*
* @param string $resourceId
*
* @return bool
*/
protected function _isAllowedAction($resourceId)
{
return $this->_authorization->isAllowed($resourceId);
}
/**
* Get form action URL.
*
* @return string
*/
public function getFormActionUrl()
{
if ($this->hasFormActionUrl()) {
return $this->getData('form_action_url');
}
return $this->getUrl('*/*/save');
}
}
17. Create Edit Form
Create block file of form with field Form.php in app/code/Thecoachsmb/Grid/Block/Adminhtml/Grid/Edit
<?php
namespace Thecoachsmb\Grid\Block\Adminhtml\Grid\Edit;
class Form extends \Magento\Backend\Block\Widget\Form\Generic
{
/**
* @var \Magento\Store\Model\System\Store
*/
protected $_systemStore;
/**
* @param \Magento\Backend\Block\Template\Context $context
* @param \Magento\Framework\Registry $registry
* @param \Magento\Framework\Data\FormFactory $formFactory
* @param array $data
*/
public function __construct(
\Magento\Backend\Block\Template\Context $context,
\Magento\Framework\Registry $registry,
\Magento\Framework\Data\FormFactory $formFactory,
array $data = []
)
{
parent::__construct($context, $registry, $formFactory, $data);
}
/**
* Prepare form.
*
* @return $this
*/
protected function _prepareForm()
{
$dateFormat = $this->_localeDate->getDateFormat(\IntlDateFormatter::SHORT);
$model = $this->_coreRegistry->registry('row_data');
$form = $this->_formFactory->create(
['data' => [
'id' => 'edit_form',
'enctype' => 'multipart/form-data',
'action' => $this->getData('action'),
'method' => 'post'
]
]
);
$form->setHtmlIdPrefix('smb_');
if ($model->getArticleId()) {
$fieldset = $form->addFieldset(
'base_fieldset',
['legend' => __('Edit Article Data'), 'class' => 'fieldset-wide']
);
$fieldset->addField('article_id', 'hidden', ['name' => 'article_id']);
} else {
$fieldset = $form->addFieldset(
'base_fieldset',
['legend' => __('Add Article Data'), 'class' => 'fieldset-wide']
);
}
$fieldset->addField(
'title',
'text',
[
'name' => 'title',
'label' => __('Title'),
'id' => 'title',
'title' => __('Title'),
'class' => 'required-entry',
'required' => true,
]
);
$form->setValues($model->getData());
$form->setUseContainer(true);
$this->setForm($form);
return parent::_prepareForm();
}
}
18. Save Data in the Database
Create Save.php in app/code/Thecoachsmb/Grid/Controller/Adminhtml/Grid for save record
<?php
namespace Thecoachsmb\Grid\Controller\Adminhtml\Grid;
class Save extends \Magento\Backend\App\Action
{
/**
* @var \Thecoachsmb\Grid\Model\GridFactory
*/
var $gridFactory;
/**
* @param \Magento\Backend\App\Action\Context $context
* @param \Thecoachsmb\Grid\Model\GridFactory $gridFactory
*/
public function __construct(
\Magento\Backend\App\Action\Context $context,
\Thecoachsmb\Grid\Model\GridFactory $gridFactory
) {
parent::__construct($context);
$this->gridFactory = $gridFactory;
}
/**
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function execute()
{
$data = $this->getRequest()->getPostValue();
if (!$data) {
$this->_redirect('thecoachsmb/grid/addrow');
return;
}
try {
$rowData = $this->gridFactory->create();
$rowData->setData($data);
if (isset($data['id'])) {
$rowData->setEntityId($data['id']);
}
$rowData->save();
$this->messageManager->addSuccess(__('Row data has been successfully saved.'));
} catch (\Exception $e) {
$this->messageManager->addError(__($e->getMessage()));
}
$this->_redirect('thecoachsmb/grid/index');
}
/**
* @return bool
*/
protected function _isAllowed()
{
return $this->_authorization->isAllowed('Thecoachsmb_Grid::save');
}
}
That’s it.

