The form.xml Skeleton: Fieldsets, Fields, DataProvider
The form.xml Skeleton: Fieldsets, Fields, DataProvider
~9 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026
Forms follow the same basic principle as grids: an XML declaration under view/adminhtml/ui_component/, a PHP DataProvider class - this time for exactly one record instead of a list - and a controller that ties both together via the Layout XML.
Edit controller and layout
<?php
declare(strict_types=1);
namespace Mironsoft\Announcement\Controller\Adminhtml\Announcement;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\View\Result\Page;
use Magento\Framework\View\Result\PageFactory;
/**
* Renders the announcement edit form page.
*/
class Edit extends Action
{
public const ADMIN_RESOURCE = 'Mironsoft_Announcement::announcement';
/**
* @param Context $context Backend action context.
* @param PageFactory $resultPageFactory Factory for the page result.
*/
public function __construct(
Context $context,
private readonly PageFactory $resultPageFactory,
) {
parent::__construct($context);
}
/**
* Builds the edit form page result.
*
* @return Page
*/
public function execute(): Page
{
$id = (int) $this->getRequest()->getParam('id');
$resultPage = $this->resultPageFactory->create();
$resultPage->setActiveMenu('Mironsoft_Announcement::announcement');
$resultPage->getConfig()->getTitle()->prepend(
$id ? __('Edit Announcement') : __('New Announcement')
);
return $resultPage;
}
}<?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">
<uiComponent name="mironsoft_announcement_form"/>
</referenceContainer>
</body>
</page>The form.xml skeleton
<?xml version="1.0"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_components.xsd">
<argument name="data" xsi:type="array">
<item name="js_config" xsi:type="array">
<item name="provider" xsi:type="string">mironsoft_announcement_form.announcement_form_data_source</item>
</item>
</argument>
<settings>
<buttons>
<button name="back" class="Mironsoft\Announcement\Block\Adminhtml\Announcement\Edit\BackButton"/>
<button name="save" class="Mironsoft\Announcement\Block\Adminhtml\Announcement\Edit\SaveButton"/>
</buttons>
<namespace>mironsoft_announcement_form</namespace>
<dataScope>data</dataScope>
<deps>
<dep>mironsoft_announcement_form.announcement_form_data_source</dep>
</deps>
</settings>
<dataSource name="announcement_form_data_source">
<argument name="dataProvider" xsi:type="configurableObject">
<argument name="class" xsi:type="string">Mironsoft\Announcement\Ui\DataProvider\Form\AnnouncementDataProvider</argument>
<argument name="name" xsi:type="string">announcement_form_data_source</argument>
<argument name="primaryFieldName" xsi:type="string">announcement_id</argument>
<argument name="requestFieldName" xsi:type="string">id</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>
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="submit_url" xsi:type="url" path="mironsoft_announcement/announcement/save"/>
</item>
</argument>
</dataSource>
<fieldset name="general">
<settings>
<label translate="true">General Information</label>
</settings>
<field name="title" formElement="input">
<settings>
<dataType>text</dataType>
<label translate="true">Title</label>
</settings>
</field>
</fieldset>
</form>The submit_url entry in the dataSource defines where the save button POSTs to - without this entry, the form would try to call itself, which almost always results in a 404.
The form DataProvider
Unlike the grid, this DataProvider returns exactly one record, identified via requestFieldName (here id from the URL). The base class is the same as for the grid:
<?php
declare(strict_types=1);
namespace Mironsoft\Announcement\Ui\DataProvider\Form;
use Magento\Ui\DataProvider\AbstractDataProvider;
use Mironsoft\Announcement\Model\ResourceModel\Announcement\CollectionFactory;
/**
* Supplies a single announcement record to the edit form.
*/
class AnnouncementDataProvider extends AbstractDataProvider
{
/**
* @param string $name Component name, injected by the UI Component framework.
* @param string $primaryFieldName Primary key column of the underlying collection.
* @param string $requestFieldName Request parameter name carrying the current ID.
* @param CollectionFactory $collectionFactory Factory building the announcement collection.
* @param array<string, mixed> $meta Additional UI Component meta configuration.
* @param array<string, mixed> $data Additional UI Component data configuration.
*/
public function __construct(
string $name,
string $primaryFieldName,
string $requestFieldName,
CollectionFactory $collectionFactory,
array $meta = [],
array $data = [],
) {
parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
$this->collection = $collectionFactory->create();
}
}Tipp: AbstractDataProvider::getData() automatically filters the collection down to the ID from requestFieldName and returns the result in the shape [$id => [...field values]] - exactly the format the JavaScript form side expects. For a "create new" call (no ID in the URL), the array simply stays empty and the form shows blank fields.
Save and back buttons
The classes referenced in <buttons> implement ButtonProviderInterface and only supply configuration data (label, class, sort order) for the toolbar - no logic of their own. Chapter 22 goes deeper into custom toolbar buttons; for now, a minimal save button is enough:
class SaveButton implements ButtonProviderInterface
{
public function getButtonData(): array
{
return [
'label' => __('Save'),
'class' => 'save primary',
'data_attribute' => ['mage-init' => ['button' => ['event' => 'save']]],
'sort_order' => 90,
];
}
}Chapter 10 fills the fieldset with more field types, chapter 12 implements the save controller that submit_url points to.