Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Implementing the Delete Controller and Confirmation Dialog

Implementing the Delete Controller and Confirmation Dialog

~8 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026

Chapter 8 already built MassDelete for several selected rows. Deleting a single row directly from the actions column needs its own delete controller plus a client-side confirmation dialog that works without any custom JavaScript - Magento_Ui already ships both.

The generic Actions column class from chapter 6 only generates an edit link by default. A custom column renderer adds the delete link with a confirmation dialog - chapter 19 goes deeper into the full custom renderer pattern; here's the concrete actions class for testimonials up front:

app/code/Mironsoft/Testimonial/Ui/Component/Listing/Column/TestimonialActions.php
<?php

declare(strict_types=1);

namespace Mironsoft\Testimonial\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;

/**
 * Renders edit and delete links for each testimonial grid row.
 */
class TestimonialActions extends Column
{
    private const URL_PATH_EDIT = 'mironsoft_testimonial/testimonial/edit';
    private const URL_PATH_DELETE = 'mironsoft_testimonial/testimonial/delete';

    /**
     * @param ContextInterface $context Rendering context.
     * @param UiComponentFactory $uiComponentFactory Factory for nested UI components.
     * @param UrlInterface $urlBuilder Builds admin URLs for the row actions.
     * @param array<string, mixed> $components Nested UI components.
     * @param array<string, mixed> $data Additional UI Component data configuration.
     */
    public function __construct(
        ContextInterface $context,
        UiComponentFactory $uiComponentFactory,
        private readonly UrlInterface $urlBuilder,
        array $components = [],
        array $data = [],
    ) {
        parent::__construct($context, $uiComponentFactory, $components, $data);
    }

    /**
     * Adds edit/delete URLs to every grid row of this column.
     *
     * @param array<string, mixed> $dataSource Raw grid data source.
     * @return array<string, mixed>
     */
    public function prepareDataSource(array $dataSource): array
    {
        if (!isset($dataSource['data']['items'])) {
            return $dataSource;
        }

        foreach ($dataSource['data']['items'] as &$item) {
            $itemId = (int) $item['testimonial_id'];

            $item[$this->getData('name')]['edit'] = [
                'href' => $this->urlBuilder->getUrl(self::URL_PATH_EDIT, ['id' => $itemId]),
                'label' => __('Edit'),
            ];
            $item[$this->getData('name')]['delete'] = [
                'href' => $this->urlBuilder->getUrl(self::URL_PATH_DELETE, ['id' => $itemId]),
                'label' => __('Delete'),
                'confirm' => [
                    'title' => __('Delete Testimonial'),
                    'message' => __('Are you sure you want to delete testimonial "%1"?', $item['customer_name']),
                ],
            ];
        }

        return $dataSource;
    }
}

The confirm configuration in the array is already fully sufficient to trigger Magento_Ui/js/modal/confirm on the client before the delete link actually fires - no custom JavaScript needed.

<actionsColumn name="actions" class="Mironsoft\Testimonial\Ui\Component\Listing\Column\TestimonialActions">
    <settings>
        <indexField>testimonial_id</indexField>
        <resizeEnabled>false</resizeEnabled>
        <sortable>false</sortable>
    </settings>
</actionsColumn>

Delete controller

app/code/Mironsoft/Testimonial/Controller/Adminhtml/Testimonial/Delete.php
<?php

declare(strict_types=1);

namespace Mironsoft\Testimonial\Controller\Adminhtml\Testimonial;

use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\NoSuchEntityException;
use Mironsoft\Testimonial\Api\TestimonialRepositoryInterface;

/**
 * Deletes a single testimonial identified via the id request parameter.
 */
class Delete extends Action
{
    public const ADMIN_RESOURCE = 'Mironsoft_Testimonial::delete';

    /**
     * @param Context $context Backend action context.
     * @param TestimonialRepositoryInterface $testimonialRepository Loads/deletes testimonials.
     */
    public function __construct(
        Context $context,
        private readonly TestimonialRepositoryInterface $testimonialRepository,
    ) {
        parent::__construct($context);
    }

    /**
     * Deletes the requested testimonial and redirects back to the grid.
     *
     * @return ResultInterface
     */
    public function execute(): ResultInterface
    {
        $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
        $id = (int) $this->getRequest()->getParam('id');

        if ($id) {
            try {
                $testimonial = $this->testimonialRepository->getById($id);
                $this->testimonialRepository->delete($testimonial);
                $this->messageManager->addSuccessMessage(__('You deleted the testimonial.'));
            } catch (NoSuchEntityException $exception) {
                $this->messageManager->addErrorMessage(__('This testimonial no longer exists.'));
            }
        }

        return $resultRedirect->setPath('mironsoft_testimonial/testimonial/index');
    }
}

This requires adding delete() to TestimonialRepositoryInterface - a thin method that internally calls $this->resource->delete($testimonial), analogous to save() from chapter 16.

Worth noting: ADMIN_RESOURCE here deliberately points at Mironsoft_Testimonial::delete, not the general ::testimonial node from chapter 14 - that separation gets formally reflected in the ACL in chapter 25.

Tipp: For a single row, the confirmation dialog leads to exactly one GET request to the delete URL after confirming - an accidental mass deletion via double-click is practically ruled out, as long as the dialog isn't accidentally left out.