Create a custom module using Magento 2 UI component

This article will demonstrate how to create a UI component in Magento 2 and how to implement CRUD operations, such as save, delete, update, and massDelete records.

In this example, we use a table with the below structure:

CREATE TABLE `pl_exampleui_news` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) DEFAULT NULL,
`content` text DEFAULT NULL,
`url_key` varchar(255) DEFAULT NULL,
`publish_date` datetime DEFAULT NULL,
`is_active` tinyint(10) DEFAULT 1,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4;

Let’s follow the steps below for creating a UI grid  and form in Magento 2

Create Models

Create a News.php in PL/ExampleUI/Model folder

namespace PL\ExampleUI\Model;

use Magento\Framework\Data\Collection\AbstractDb;
use Magento\Framework\Model\AbstractModel;
use Magento\Framework\Model\Context;
use Magento\Framework\Model\ResourceModel\AbstractResource;
use Magento\Framework\Registry;
use PL\ExampleUI\Model\ResourceModel\News\CollectionFactory as NewsCollectionFactory;

class News extends AbstractModel
{
protected $newsCollectionFactory;

public function __construct(
Context $context,
Registry $registry,
NewsCollectionFactory $newsCollectionFactory,
AbstractResource $resource = null,
AbstractDb $resourceCollection = null,
array $data = []
) {
$this->newsCollectionFactory = $newsCollectionFactory;

parent::__construct($context, $registry, $resource, $resourceCollection, $data);
}


protected function _construct()
{
$this->_init(\PL\ExampleUI\Model\ResourceModel\News::class);
}
}

Create a News.php in PL/ExampleUI/Model/ResourceModel folder

namespace PL\ExampleUI\Model\ResourceModel;

use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
use Magento\Framework\Model\ResourceModel\Db\Context;
use Magento\Framework\Filter\TranslitUrl;

class News extends AbstractDb
{
protected $tranlistUrl;

public function __construct(
Context $context,
TranslitUrl $tranlistUrl,
$connectionName = null
) {
parent::__construct(
$context,
$connectionName
);
$this->tranlistUrl = $tranlistUrl;
}

protected function _construct()
{
$this->_init('pl_exampleui_news', 'id');
}

protected function _beforeSave(\Magento\Framework\DataObject $object)
{
if($object->isObjectNew()){
$urlKey = $this->tranlistUrl->filter($object->getTitle());
$object->setUrlKey($urlKey);
}
return parent::_beforeSave($object);
}
}

Create a Collection.php file in PL/ExampleUI/Model/ResourceModel/News folder

namespace PL\ExampleUI\Model\ResourceModel\News;

use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
use Magento\Framework\Api\SearchResultsInterface;

class Collection extends AbstractCollection implements SearchResultsInterface
{
protected $_idFieldName = 'id';

protected function _construct()
{
$this->_init(\PL\ExampleUI\Model\News::class,
\PL\ExampleUI\Model\ResourceModel\News::class
);
}
public function getSearchCriteria()
{
return null;
}
public function setSearchCriteria(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria = null)
{
return $this;
}
public function getTotalCount()
{
return $this->getSize();
}
public function setTotalCount($totalCount)
{
return $this;
}
public function setItems(array $items = null)
{
return $this;
}
}

Create Router For Controller

You need to create a routes.xml file in PL/ExampleUI/view/adminhtml/layout folder.

<?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="admin_exampleui" frontName="admin_exampleui">
<module name="PL_ExampleUI"/>
</route>
</router>
</config>


Create a menu.xml file in PL/ExampleUI/view/adminhtml/layout folder.


<?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="PL_ExampleUI::news" title="News" module="PL_ExampleUI" sortOrder="110" resource="PL_ExampleUI::news" action="admin_exampleui/news" parent="Magento_Backend::content_elements"/>
</menu>
</config>


Create Controlers

create a News.php file in PL/ExampleUI/Controller/Adminhtml

namespace PL\ExampleUI\Controller\Adminhtml;

abstract class News extends \Magento\Backend\App\Action
{
const ADMIN_RESOURCE = 'PL_ExampleUI::news';

protected $newsFactory;

protected $coreRegistry;

public function __construct(
\Magento\Backend\App\Action\Context $context,
\Magento\Framework\Registry $coreRegistry,
\PL\ExampleUI\Model\NewsFactory $newsFactory
) {
parent::__construct($context);
$this->newsFactory = $newsFactory;
$this->coreRegistry = $coreRegistry;
}

public function initNews()
{
$newsId = (int)$this->getRequest()->getParam('id');
$news = $this->newsFactory->create();
if ($newsId) {
$news->load($newsId);
if (!$news->getId()) {
$this->messageManager->addErrorMessage(__('This record no longer exists.'));
return false;
}
}
return $news;
}
}

Create a Index.php file in PL\ExampleUI\Controller\Adminhtml\News

namespace PL\ExampleUI\Controller\Adminhtml\News;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;

class Index extends Action
{
public $resultPageFactory;

public $resultPage;

public function __construct(
Context $context,
PageFactory $resultPageFactory
) {
$this->resultPageFactory = $resultPageFactory;
parent::__construct($context);
}

public function execute()
{
$resultPage = $this->resultPageFactory->create();
$resultPage->getConfig()->getTitle()->prepend(__('News'));
return $resultPage;
}
}

Create a NewAction.php file in PL\ExampleUI\Controller\Adminhtml\News

namespace PL\ExampleUI\Controller\Adminhtml\News;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Backend\Model\View\Result\ForwardFactory;

class NewAction extends Action
{
public $resultForwardFactory;

public function __construct(
ForwardFactory $resultForwardFactory,
Context $context
) {
$this->resultForwardFactory = $resultForwardFactory;
parent::__construct($context);
}

public function execute()
{
$resultForward = $this->resultForwardFactory->create();
$resultForward->forward('edit');
return $resultForward;
}
}

Create a Edit.php file in PL\ExampleUI\Controller\Adminhtml\News folder

namespace PL\ExampleUI\Controller\Adminhtml\News;

use PL\ExampleUI\Controller\Adminhtml\News;

class Edit extends News
{
public $resultPageFactory;

public function __construct(
\Magento\Backend\App\Action\Context $context,
\Magento\Framework\Registry $coreRegistry,
\PL\ExampleUI\Model\NewsFactory $newsFactory,
\Magento\Framework\View\Result\PageFactory $resultPageFactory
) {
parent::__construct(
$context,
$coreRegistry,
$newsFactory
);
$this->resultPageFactory = $resultPageFactory;
}

public function execute()
{
$news = $this->initNews();
if (!$news) {
$resultRedirect = $this->resultRedirectFactory->create();
$resultRedirect->setPath('*');
return $resultRedirect;
}
$data = $this->_session->getData('exampleui_news', true);
if (!empty($data)) {
$news->setData($data);
}
$this->coreRegistry->register('exampleui_news', $news);
$resultPage = $this->resultPageFactory->create();
$resultPage->setActiveMenu('PL_ExampleUI::news');
$resultPage->getConfig()->getTitle()->set(__('News'));
$title = $news->getId() ? $news->getTitle() : __('Add News');
$resultPage->getConfig()->getTitle()->prepend($title);
return $resultPage;
}
}

Create a Save.php file in PL\ExampleUI\Controller\Adminhtml\News folder

namespace PL\ExampleUI\Controller\Adminhtml\News;

use PL\ExampleUI\Controller\Adminhtml\News;

class Save extends News
{
public function __construct(
\Magento\Backend\App\Action\Context $context,
\Magento\Framework\Registry $coreRegistry,
\PL\ExampleUI\Model\NewsFactory $newsFactory
) {
parent::__construct(
$context,
$coreRegistry,
$newsFactory
);
}

public function execute()
{
$resultRedirect = $this->resultRedirectFactory->create();
if ($data = $this->getRequest()->getParams()) {
$news = $this->initNews();
$idFieldName = $news->getResource()->getIdFieldName();
if (isset($data[$idFieldName]) && empty($data[$idFieldName])) {
unset($data[$idFieldName]);
}
$news->addData($data);
try {
$news->save();
$this->messageManager->addSuccessMessage(__('Saved.'));
$resultRedirect->setPath('admin_exampleui/*/');
return $resultRedirect;
} catch (\Exception $e) {
$this->messageManager->addErrorMessage($e->getMessage());
}
$this->_getSession()->setData('exampleui_news_data', $data);
$resultRedirect->setPath('admin_exampleui/*/edit', ['id' => $news->getId(), '_current' => true]);
return $resultRedirect;
}
}
}

Create a Delete.php file in PL\ExampleUI\Controller\Adminhtml\News folder

namespace PL\ExampleUI\Controller\Adminhtml\News;

use PL\ExampleUI\Controller\Adminhtml\News;

class Delete extends News
{

public function execute()
{
$resultRedirect = $this->resultRedirectFactory->create();
if ($id = $this->getRequest()->getParam('id')) {
try {
$this->newsFactory->create()
->load($id)
->delete();
$this->messageManager->addSuccessMessage(__('Deleted.'));
} catch (\Exception $e) {
$this->messageManager->addErrorMessage($e->getMessage());
$resultRedirect->setPath('admin_exampleui/*/edit', ['id' => $id]);

return $resultRedirect;
}
} else {
$this->messageManager->addErrorMessage(__('Record to delete was not found.'));
}

$resultRedirect->setPath('admin_exampleui/*/');
return $resultRedirect;
}
}

Create a MassDelete.php file in PL\ExampleUI\Controller\Adminhtml\News folder

namespace PL\ExampleUI\Controller\Adminhtml\News;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\ResultFactory;
use Magento\Ui\Component\MassAction\Filter;
use PL\ExampleUI\Model\ResourceModel\News\CollectionFactory as NewsCollectionFactory;

class MassDelete extends Action
{

public $filter;

public $newsCollectionFactory;

public function __construct(
Context $context,
Filter $filter,
NewsCollectionFactory $newsCollectionFactory
) {
$this->filter = $filter;
$this->newsCollectionFactory = $newsCollectionFactory;

parent::__construct($context);
}

public function execute()
{
$collection = $this->filter->getCollection($this->newsCollectionFactory->create());
try {
$collection->walk('delete');
$this->messageManager->addSuccessMessage(__('Deleted.'));
} catch (\Exception $e) {
$this->messageManager->addSuccessMessage(__('Something wrong.'));
}
$resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);

return $resultRedirect->setPath('*/*/');
}
}

Create Blocks

Create a GenericButton.php file in PL/ExampleUI/Block/Adminhtml/News folder

namespace PL\ExampleUI\Block\Adminhtml\News;

use Magento\Backend\Block\Widget\Context;

class GenericButton
{
protected $context;

protected $newsFactory;

public function __construct(
Context $context,
\PL\ExampleUI\Model\NewsFactory $newsFactory
) {
$this->context = $context;
$this->newsFactory = $newsFactory;
}

public function getNewsId()
{
return $this->context->getRequest()->getParam('id');
}

public function getUrl($route = '', $params = [])
{
return $this->context->getUrlBuilder()->getUrl($route, $params);
}
}

Create a BackButton.php file in PL/ExampleUI/Block/Adminhtml/News folder

namespace PL\ExampleUI\Block\Adminhtml\News;

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

class BackButton extends GenericButton implements ButtonProviderInterface
{

public function getButtonData()
{
return [
'label' => __('Back'),
'on_click' => sprintf("location.href = '%s';", $this->getBackUrl()),
'class' => 'back',
'sort_order' => 10
];
}

public function getBackUrl()
{
return $this->getUrl('*/*/');
}
}

Create a SaveButton.php file in PL/ExampleUI/Block/Adminhtml/News folder

namespace PL\ExampleUI\Block\Adminhtml\News;

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

class SaveButton extends GenericButton implements ButtonProviderInterface
{
public function getButtonData()
{
return [
'label' => __('Save'),
'class' => 'save primary',
'data_attribute' => [
'mage-init' => [
'buttonAdapter' => [
'actions' => [
[
'targetName' => 'exampleui_news_form.exampleui_news_form',
'actionName' => 'save',
'params' => [false]
]
]
]
]
]
];
}
}

Create a DeleteButton.php file in PL/ExampleUI/Block/Adminhtml/News folder

namespace PL\ExampleUI\Block\Adminhtml\News;

use Magento\Framework\View\Element\UiComponent\Control\ButtonProviderInterface;

class DeleteButton extends GenericButton implements ButtonProviderInterface
{
public function getButtonData()
{
$data = [];
if ($this->getNewsId()) {
$data = [
'label' => __('Delete'),
'class' => 'delete',
'on_click' => 'deleteConfirm(\'' . __(
'Are you sure you want to do this?'
) . '\', \'' . $this->getDeleteUrl() . '\', {"data": {}})',
'sort_order' => 20,
];
}
return $data;
}

public function getDeleteUrl()
{
return $this->getUrl('*/*/delete', ['id' => $this->getNewsId()]);
}
}

Create UI Components

Create a Actions.php file in PL/ExampleUI/Ui/Component/Listing/Column folder

namespace PL\ExampleUI\Ui\Component\Listing\Column;

use Magento\Framework\UrlInterface;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Ui\Component\Listing\Columns\Column;

class Actions extends Column
{

protected $urlBuilder;

public function __construct(
ContextInterface $context,
UiComponentFactory $uiComponentFactory,
UrlInterface $urlBuilder,
array $components = [],
array $data = []
) {
$this->urlBuilder = $urlBuilder;

parent::__construct($context, $uiComponentFactory, $components, $data);
}

public function prepareDataSource(array $dataSource)
{
if (isset($dataSource['data']['items'])) {
foreach ($dataSource['data']['items'] as &$item) {
$actions = $this->getData('action_list');
foreach ($actions as $key => $action) {
$params = $action['params'];
foreach ($params as $field => $param) {
$params[$field] = $item[$param];
}

$item[$this->getData('name')][$key] = [
'href' => $this->urlBuilder->getUrl(
$action['path'],
['id' => $item[$action['params']['id']]]
),
'label' => $action['label'],
'hidden' => false,
];
}
}
}

return $dataSource;
}
}

Create a NewsDataProvider.php file in PL/ExampleUI/Ui/Component/Listing/Column folder

namespace PL\ExampleUI\Ui\DataProvider\Form;

use PL\ExampleUI\Model\ResourceModel\News\CollectionFactory as NewsCollectionFactory;
use Magento\Ui\DataProvider\AbstractDataProvider;

class NewsDataProvider extends AbstractDataProvider
{
protected $loadedData;

public function __construct(
$name,
$primaryFieldName,
$requestFieldName,
NewsCollectionFactory $newsCollectionFactory,
array $meta = [],
array $data = []
) {
parent::__construct(
$name,
$primaryFieldName,
$requestFieldName,
$meta,
$data
);
$this->collection = $newsCollectionFactory->create();
}

public function getData()
{
if (isset($this->loadedData)) {
return $this->loadedData;
}
$items = $this->collection->getItems();
foreach ($items as $news) {
$this->loadedData[$news->getId()] = $news->getData();
}
return $this->loadedData;
}
}

Create a exampleui_news_listing.xml file in PL/ExampleUI/view/adminhtml/ui_component folder

<?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">
<argument name="data" xsi:type="array">
<item name="js_config" xsi:type="array">
<item name="provider" xsi:type="string">exampleui_news_listing.exampleui_news_listing_data_source</item>
<item name="deps" xsi:type="string">exampleui_news_listing.exampleui_news_listing_data_source</item>
</item>
<item name="spinner" xsi:type="string">exampleui_news_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</item>
<item name="class" xsi:type="string">primary</item>
<item name="url" xsi:type="string">*/*/new</item>
</item>
</item>
</argument>
<dataSource name="exampleui_news_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">exampleui_news_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="indexField" xsi:type="string">id</item>
</item>
</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>
<listingToolbar name="listing_top">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="sticky" xsi:type="boolean">true</item>
</item>
</argument>
<bookmark name="bookmarks"/>
<columnsControls name="columns_controls"/>
<exportButton name="export_button"/>
<filters name="listing_filters"/>
<massaction name="listing_massaction">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="component" xsi:type="string">Magento_Ui/js/grid/tree-massactions</item>
</item>
</argument>
<action name="delete">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="type" xsi:type="string">delete</item>
<item name="label" xsi:type="string" translate="true">Delete</item>
<item name="url" xsi:type="url" path="admin_exampleui/news/massDelete"/>
<item name="confirm" xsi:type="array">
<item name="title" xsi:type="string" translate="true">Delete</item>
<item name="message" xsi:type="string" translate="true">Are you sure you wan't to delete selected records?</item>
</item>
</item>
</argument>
</action>
</massaction>
<paging name="listing_paging"/>
</listingToolbar>
<columns name="exampleui_news_columns">

<selectionsColumn name="ids">
<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">55</item>
<item name="indexField" xsi:type="string">ids</item>
</item>
</argument>
</selectionsColumn>
<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>
</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="editor" xsi:type="array">
<item name="editorType" xsi:type="string">text</item>
<item name="validation" xsi:type="array">
<item name="required-entry" xsi:type="boolean">true</item>
</item>
</item>
<item name="label" xsi:type="string" translate="true">Title</item>
</item>
</argument>
</column>
<column name="url_key">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="label" xsi:type="string" translate="true">URL Key</item>
</item>
</argument>
</column>
<column name="publish_date" 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">Publish Date</item>
</item>
</argument>
</column>

<actionsColumn name="actions" class="PL\ExampleUI\Ui\Component\Listing\Column\Actions">
<argument name="data" xsi:type="array">
<item name="action_list" xsi:type="array">
<item name="edit" xsi:type="array">
<item name="label" xsi:type="string" translate="true">Edit</item>
<item name="path" xsi:type="string">admin_exampleui/news/edit</item>
<item name="params" xsi:type="array">
<item name="id" xsi:type="string">id</item>
</item>
</item>
</item>
</argument>
</actionsColumn>
</columns>
</listing>

Create a exampleui_news_form.xml file in PL/ExampleUI/view/adminhtml/ui_component folder

<?xml version="1.0" encoding="UTF-8"?>

<form 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">exampleui_news_form.news_form_data_source</item>
<item name="deps" xsi:type="string">exampleui_news_form.news_form_data_source</item>
</item>
<item name="label" xsi:type="string" translate="true">News Information</item>
<item name="config" xsi:type="array">
<item name="dataScope" xsi:type="string">data</item>
<item name="namespace" xsi:type="string">exampleui_news_form</item>
</item>
<item name="template" xsi:type="string">templates/form/collapsible</item>
<item name="buttons" xsi:type="array">
<item name="back" xsi:type="string">PL\ExampleUI\Block\Adminhtml\News\BackButton</item>
<item name="delete" xsi:type="string">PL\ExampleUI\Block\Adminhtml\News\DeleteButton</item>
<item name="save" xsi:type="string">PL\ExampleUI\Block\Adminhtml\News\SaveButton</item>
</item>
</argument>
<dataSource name="news_form_data_source">
<argument name="dataProvider" xsi:type="configurableObject">
<argument name="class" xsi:type="string">PL\ExampleUI\Ui\DataProvider\Form\NewsDataProvider</argument>
<argument name="name" xsi:type="string">news_form_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="submit_url" xsi:type="url" path="admin_exampleui/news/save"/>
</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/form/provider</item>
</item>
</argument>
</dataSource>
<fieldset name="general" sortOrder="0">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="label" xsi:type="string"/>
<item name="sortOrder" xsi:type="number">10</item>
</item>
</argument>
<field name="is_active">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="dataType" xsi:type="string">boolean</item>
<item name="label" xsi:type="string" translate="true">Enable</item>
<item name="formElement" xsi:type="string">checkbox</item>
<item name="prefer" xsi:type="string">toggle</item>
<item name="source" xsi:type="string">news</item>
<item name="sortOrder" xsi:type="number">1</item>
<item name="dataScope" xsi:type="string">is_active</item>
<item name="valueMap" xsi:type="array">
<item name="true" xsi:type="number">1</item>
<item name="false" xsi:type="number">0</item>
</item>
<item name="default" xsi:type="number">1</item>
</item>
</argument>
</field>
<field name="title">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="dataType" xsi:type="string">text</item>
<item name="label" xsi:type="string" translate="true">Title</item>
<item name="formElement" xsi:type="string">input</item>
<item name="source" xsi:type="string">news</item>
<item name="sortOrder" xsi:type="number">10</item>
<item name="dataScope" xsi:type="string">title</item>
<item name="validation" xsi:type="array">
<item name="required-entry" xsi:type="boolean">true</item>
</item>
</item>
</argument>
</field>
<field name="publish_date">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="label" xsi:type="string" translate="true">Publish Date</item>
<item name="dataType" xsi:type="string">text</item>
<item name="formElement" xsi:type="string">date</item>
<item name="source" xsi:type="string">news</item>
<item name="dataScope" xsi:type="string">publish_date</item>
<item name="sortOrder" xsi:type="number">20</item>
<item name="notice" xsi:type="string" translate="true">You can set a future date to schedule the publication.</item>
</item>
</argument>
</field>

<field name="content">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="label" xsi:type="string"/>
<item name="formElement" xsi:type="string">wysiwyg</item>
<item name="source" xsi:type="string">news</item>
<item name="wysiwyg" xsi:type="boolean">true</item>
<item name="dataScope" xsi:type="string">content</item>
<item name="additionalClasses" xsi:type="string">admin__field-wide</item>
<item name="sortOrder" xsi:type="number">30</item>
</item>
</argument>
</field>
</fieldset>
</form>

Create Layouts

Create an admin_exampleui_news_index.xml file in PL/ExampleUI/view/adminhtml/layout folder

<?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">
<update handle="styles"/>
<body>
<referenceBlock name="menu">
<action method="setActive">
<argument name="itemId" xsi:type="string">PL_ExampleUI::news</argument>
</action>
</referenceBlock>
<referenceContainer name="content">
<uiComponent name="exampleui_news_listing"/>
</referenceContainer>
</body>
</page>

Create an admin_exampleui_news_edit.xml file in PL/ExampleUI/view/adminhtml/layout folder

<?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">
<uiComponent name="exampleui_news_form"/>
</referenceContainer>
</body>
</page>

Create a di.xml file in PL/ExampleUI/etc folder

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">

<type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
<arguments>
<argument name="collections" xsi:type="array">
<item name="exampleui_news_listing_data_source" xsi:type="string">PL\ExampleUI\Model\ResourceModel\News\Collection</item>
</argument>
</arguments>
</type>
<type name="Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory">
<arguments>
<argument name="collections" xsi:type="array">
<item name="exampleui_news_listing_data_source" xsi:type="string">PL\ExampleUI\Model\ResourceModel\News\Grid\Collection</item>
</argument>
</arguments>
</type>
<virtualType name="PL\ExampleUI\Model\ResourceModel\News\Grid\Collection" type="Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult">
<arguments>
<argument name="mainTable" xsi:type="string">pl_exampleui_news</argument>
<argument name="eventPrefix" xsi:type="string">pl_exampleui_news_collection</argument>
<argument name="eventObject" xsi:type="string">news_collection</argument>
<argument name="resourceModel" xsi:type="string">PL\ExampleUI\Model\ResourceModel\News</argument>
</arguments>
</virtualType>
</config>

This is a simple module using UI components; you can see more at this https://developer.adobe.com/commerce/frontend-core/ui-components/components/

Back to Top