In this article we will explore how to add a mass action in admin product listing.

First you will need to define a new mass action in listing xml. We will an action - update_custom_prices.

<?xml version="1.0" encoding="UTF-8"?>
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <listingToolbar name="listing_top">
        <massaction name="listing_massaction">
            <action name="update_custom_prices">
                <argument name="data" xsi:type="array">
                    <item name="config" xsi:type="array">
                        <item name="confirm" xsi:type="array">
                            <item name="title" xsi:type="string" translate="true">Update Custom prices</item>
                            <item name="message" xsi:type="string" translate="true">Update Custom prices for selected items?</item>
                        </item>
                        <item name="type" xsi:type="string">update_custom_prices</item>
                        <item name="label" xsi:type="string" translate="true">Update Custom prices</item>
                        <item name="url" xsi:type="url" path="sync/custom/price"/>
                    </item>
                </argument>
            </action>
        </massaction>
    </listingToolbar>
</listing>
view/adminhtml/ui_component/product_listing.xml

A few things are happening here.

  • We are hooking into the default product_listing ui component
  • We are defining a new action under the massaction node
  • We are setting the confirmation config and message
  • We are defining the custom action that will be triggered when the action is selected

Next we have to define a controller. But before we can do that we need to define our a new custom admin route. We can do this by adding a new route in 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="sync" frontName="sync">
            <module name="Techflarestudio_Sync" before="Magento_Backend"/>
        </route>
    </router>
</config>
etc/adminhtml/routes.xml

Things to note here:

  • We are defining the action in admin scope
  • We are defining the frontName for the action - sync

Once we have our router defined we can define our controller and action. Define the controller in the directory Custom and File name is Price. This will match to our previously defined path sync/custom/price.

<?php

namespace Techflarestudio\Sync\Controller\Adminhtml\Custom;

use Magento\Backend\Model\View\Result\Redirect;
use Magento\Catalog\Controller\Adminhtml\Product;
use Magento\Catalog\Controller\Adminhtml\Product\Builder;
use Magento\Framework\Controller\ResultFactory;
use Magento\Backend\App\Action\Context;
use Magento\Ui\Component\MassAction\Filter;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\Backend\Model\View\Result\Redirect;
use Magento\Catalog\Controller\Adminhtml\Product;
use Magento\Catalog\Controller\Adminhtml\Product\Builder;
use Magento\Framework\Controller\ResultFactory;

class Price extends Product
{
    /**
     * Massactions filter
     *
     * @var Filter
     */
    protected $filter;
    /**
     * @var CollectionFactory
     */
    protected $collectionFactory;
    /**
     * @param Context $context
     * @param Builder $productBuilder
     * @param Filter $filter
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(
        Context $context,
        Builder $productBuilder,
        Filter $filter,
        CollectionFactory $collectionFactory
    ) {
        $this->filter = $filter;
        $this->collectionFactory = $collectionFactory;
        parent::__construct($context, $productBuilder);
    }
    /**
     * @return Redirect
     */
    public function execute()
    {
        $collection = $this->filter->getCollection($this->collectionFactory->create());
        $productIds = $collection->getAllIds();
        
        /**
         *  Custom price update logic
         */

        $this->messageManager->addSuccess(
            __('A total of %1 records have been send to custom platfrom.', count($productIds))
        );
        return $this->resultFactory->create(ResultFactory::TYPE_REDIRECT)->setPath('catalog/*/index');
    }
}
Controller/Adminhtml/Custom/Price.php

We are extending the core controller class for product actions Magento\Catalog\Controller\Adminhtml\Product. This is not mandatory but we are able to reuse the same ACLs as the delete and other core mass actions. We could have just as well directly extended the core Magento\Backend\App\Action class.

That is it. Once you visit the product grid you should be able to see your action and execute it.