Building Custom Toolbar Buttons for the UI Component
Building Custom Toolbar Buttons for the UI Component
~8 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026
Chapter 9 showed a minimal save button. This chapter goes deeper into the button pattern and adds "save and continue editing" as well as "duplicate" to the testimonial form - both through the same ButtonProviderInterface.
ButtonProviderInterface in detail
Every button is a pure configuration class - no logic, just data describing what the button looks like and which JavaScript event it triggers on click:
<?php
declare(strict_types=1);
namespace Mironsoft\Testimonial\Block\Adminhtml\Testimonial\Edit;
use Magento\Ui\Component\Control\Container\ToolbarButtonProviderInterface as ButtonProviderInterface;
/**
* Configures the "Save and Continue Edit" toolbar button.
*/
class SaveAndContinueButton implements ButtonProviderInterface
{
/**
* Returns the button configuration array.
*
* @return array<string, mixed>
*/
public function getButtonData(): array
{
return [
'label' => __('Save and Continue Edit'),
'class' => 'save',
'data_attribute' => [
'mage-init' => [
'button' => ['event' => 'saveAndContinueEdit'],
],
],
'sort_order' => 80,
];
}
}The saveAndContinueEdit event is already wired into Magento_Ui's JavaScript form provider - it automatically sets the back=edit parameter on submit, exactly the parameter the save controller from chapter 12 already reads.
A duplicate button with its own controller
class DuplicateButton implements ButtonProviderInterface
{
public function __construct(private readonly Context $context)
{
}
public function getButtonData(): array
{
$id = (int) $this->context->getRequest()->getParam('id');
if (!$id) {
return [];
}
return [
'label' => __('Duplicate'),
'class' => 'duplicate',
'on_click' => sprintf(
"location.href = '%s';",
$this->context->getUrlBuilder()->getUrl(
'mironsoft_testimonial/testimonial/duplicate',
['id' => $id]
)
),
'sort_order' => 40,
];
}
}An empty array (for a new, not-yet-saved record) means: no button - getButtonData() is allowed to return an empty array to conditionally hide a button entirely, without any extra visibility logic in the XML.
Registering buttons in form.xml
<settings>
<buttons>
<button name="back" class="Mironsoft\Testimonial\Block\Adminhtml\Testimonial\Edit\BackButton"/>
<button name="delete" class="Mironsoft\Testimonial\Block\Adminhtml\Testimonial\Edit\DeleteButton"/>
<button name="duplicate" class="Mironsoft\Testimonial\Block\Adminhtml\Testimonial\Edit\DuplicateButton"/>
<button name="save_and_continue" class="Mironsoft\Testimonial\Block\Adminhtml\Testimonial\Edit\SaveAndContinueButton"/>
<button name="save" class="Mironsoft\Testimonial\Block\Adminhtml\Testimonial\Edit\SaveButton"/>
</buttons>
</settings>The left-to-right arrangement is driven by sort_order, not the order in the XML - a lower number sits further to the left.
Toolbar buttons for the grid
The "Add New" button above the grid itself follows the same pattern, but is registered directly through the grid controller as an addButton() call, not via form.xml - a small but often overlooked inconsistency between the grid and form toolbars in Magento.
Tipp: For every new button, it's worth briefly checking whether a comparable event already exists in the JavaScript form provider (like saveAndContinueEdit above) - that often avoids a completely custom Ajax call.