magepsycho / magento2-async-bulk-operations-ui
Magento 2 Async Bulk Operations UI
Package info
github.com/MagePsycho/magento2-async-bulk-operations-ui
Type:magento2-module
pkg:composer/magepsycho/magento2-async-bulk-operations-ui
README
Magento 2 Async Bulk Operations UI
Overview
Magento 2 Async Bulk Operations UI extension gives you a complete admin view of everything Magento_AsynchronousOperations is doing in your store.
Magento Open Source does ship a Bulk Actions grid, but it only shows bulks created by the admin who is logged in, reports a bulk as failed the moment one operation fails, and offers no counts, no progress and no aggregate view. This extension replaces that narrow window with two full grids and a status dashboard — so "did the import finish", "which topic keeps failing" and "what is still queued" are answerable at a glance.
Key Features
- Lists every bulk — whichever admin, integration or cron created it
- Derives bulk status from operation counts instead of
MAX(status), so one failure no longer hides work still in progress - Shows complete / failed / open counts and a progress percentage per bulk
- Adds a dedicated operations log across all bulks, filterable by topic, status and error code
- Dashboard with KPI tiles, per-day stacked bars and status doughnuts
- Inspects the request payload and stored response of any single operation, formatted, in a modal
- Retries failed operations safely, including a workaround for a core bug that strands bulks "In Progress" forever
- Separate ACL resources for viewing, retrying and deleting
- CSV / Excel export on both grids
Feature Highlights
Every Bulk, Not Just Yours
| Core grid | This extension |
|---|---|
Only bulks owned by the logged-in admin (user_id = <you>), plus every integration bulk |
Every bulk, whichever admin, integration or cron created it |
Status = MAX(status) over the operations, so one rejected operation reports the whole bulk as failed while others are still running |
Status derived from counts: not started / in progress / finished successfully / finished with failure |
| No counts, no progress | Complete / failed / open counts and a progress percentage per bulk |
| Operations only visible per bulk, in a modal | Dedicated operations log across every bulk, filterable by topic, status, error code |
| No aggregate view | Dashboard with KPI tiles, per-day stacked bars and two status doughnuts |
Pages
| Menu | Route | Contents |
|---|---|---|
| System > Action Logs > Async Bulk Operations | magepsycho_asyncoperationsui/bulk/index |
Dashboard + bulk grid |
| "Operations Log" button on the bulk grid | magepsycho_asyncoperationsui/operation/index |
Dashboard + operation grid |
| "View" row action | magepsycho_asyncoperationsui/bulk/view/uuid/<uuid> |
Bulk summary + its operations |
Only the bulk grid has a menu entry. The operations log is a diagnostic view of the same tables — topic, error code and result message exist only per operation, so it answers questions the bulk grid structurally cannot ("which topic keeps failing", "everything that failed with this message, across all bulks") — but it is not a second daily destination, so it hangs off the grid.
Row actions: View, Retry Failed, Delete. Both state-changing actions post and are confirm-gated. On the operation grids, View Payload opens the request payload and response in a modal.
Dashboard
Rendered above the grid in every layout, drawn with the Chart.js 4.4 bundled in lib/web/chartjs.
- KPI tiles: bulks, bulks in progress, bulks with failures, operations, open, failed, success rate
- Stacked bars: operations per day, split complete / failed / open, over 7 / 30 / 90 / 365 days
- Doughnuts: bulks by status, operations by status
The first payload is embedded in the page; changing the period re-fetches magepsycho_asyncoperationsui/chart/data as JSON.
Days are taken from the parent bulk's start_time, not magento_operation.started_at, which stays null until a consumer picks the operation up — open operations would otherwise have no date. Timestamps come straight from the database and are not converted to the admin locale timezone.
Bulk Details
Every bulk opens onto its own page: the summary counts, its operations with topic, status, error code and result message, and the two state-changing actions.
Payload Inspector
The View Payload action on any operation opens the request payload — pretty-printed and syntax-highlighted — next to the stored response. When an operation failed before returning a body, that is said in words rather than shown as an empty box.
This is the detail core stores and never surfaces: without it, "Invalid product data" is the whole story — with it, you can see which SKU carried the bad attribute set. The request body is unwrapped from the operation envelope, where Magento keeps it JSON-encoded twice inside meta_information.
An empty pane is normal and says so: the consumer clears serialized_data once an operation completes, and result_serialized_data only ever holds the body of a successful synchronous call.
Safe Retry
Retry collects the distinct error codes of the bulk's failed operations and hands them to BulkManagement::retryBulk() — the same thing as ticking every row in the core retry modal. Failed operations stored without an error code cannot be targeted by retryBulk(); they are counted and reported in a notice rather than silently ignored.
Core caveat, worked around here. retryBulk() reopens the operation rows and commits, then publishes. For async.* API topics the publish step throws —
Data item corresponding to "sku" must be specified in the message with topic
"async.magento.catalog.api.productrepositoryinterface.deletebyid.delete"
— because the stored payload is not in the shape the message encoder expects on re-publish. Core leaves the operations Open with nothing in the queue, so the bulk reads as In Progress forever. This extension snapshots the affected rows first and restores them if publishing throws, so a failed retry is a no-op with a clear error message. Retry of non-async.* topics is unaffected.
Access Control
ACL resources under System:
MagePsycho_AsyncOperationsUi::bulk— view both grids and the detail pageMagePsycho_AsyncOperationsUi::retry— retry failed operationsMagePsycho_AsyncOperationsUi::delete— delete a bulk
The core grid restricts rows by owner; this one does not, so the ACL resources are the only gate — grant them accordingly.
🛠️ Installation
1 Using Composer (Preferred)
composer require magepsycho/magento2-async-bulk-operations-ui
2 Using Modman
modman init
modman clone git@github.com:MagePsycho/magento2-async-bulk-operations-ui.git
3 Using Zip File
- Download the Extension Zip File
- Extract & upload the files to
/path/to/magento2/app/code/MagePsycho/AsyncOperationsUi/
After installation by either means, activate the extension with following steps
- Enable the module
php bin/magento module:enable MagePsycho_AsyncOperationsUi --clear-static-content
php bin/magento setup:upgrade
- Flush the store cache
php bin/magento cache:flush
- Deploy static content - in Production mode only
rm -rf pub/static/* var/view_preprocessed/*
php bin/magento setup:static-content:deploy
- Go to Admin > SYSTEM > Action Logs > Async Bulk Operations
The extension creates no tables of its own — it reads magento_bulk, magento_operation and admin_user.
Configuration
Stores > Configuration > MagePsycho > Async Bulk Operations
| Setting | Default | Purpose |
|---|---|---|
| Show Charts Above Grid | Yes | Hides the KPI tiles and charts when only the grids are wanted |
| Default Chart Period | 7 days | Period preselected in the "Operations per day" chart |
Developer Notes
Grid collections are registered globally, on purpose
etc/di.xml — not etc/adminhtml/di.xml — registers the grid collections on Magento\Framework\View\Element\UiComponent\DataProvider\CollectionFactory::$collections. Magento_AsynchronousOperations registers its own entries on that same argument in its global etc/di.xml, and an area-scoped array argument replaces the global array rather than merging into it. Declaring these in etc/adminhtml/di.xml unregisters the core handles and breaks the core Bulk Actions page with:
Not registered handle bulk_listing_data_source
Keep any future data-source registration in the global file.
Performance
The bulk grid derives its counts with correlated sub-selects against magento_operation, which is indexed on bulk_uuid. Fine for the page sizes of an admin grid; on installs with millions of operation rows, prefer filtering the grid before sorting on a derived column.
Demo data (development only)
# backdated bulks over the last two weeks, uuid prefix mp-seed- php app/code/MagePsycho/AsyncOperationsUi/dev/seed-demo-data.php synthetic # three genuine async bulks; existing catalog data is never touched php app/code/MagePsycho/AsyncOperationsUi/dev/seed-demo-data.php real bin/magento queue:consumers:start async.operations.all --max-messages=10 # remove the synthetic rows again (real bulks are left alone - delete those from the grid) php app/code/MagePsycho/AsyncOperationsUi/dev/seed-demo-data.php clean
Static analysis
vendor/bin/phpstan analyse -c app/code/MagePsycho/AsyncOperationsUi/phpstan.neon --memory-limit=1G
vendor/bin/phpcs --standard=Magento2 --extensions=php,phtml \
--ignore=*/dev/* app/code/MagePsycho/AsyncOperationsUi/
dev/ is excluded from both: it is a standalone CLI script that bootstraps Magento itself, which the Magento2 sniffs and phpstan rightly reject as module code.
Changelog
Version 1.0.0 (2026-08-07)
- Initial Release.
Authors
Contributors
To Contribute
Any contribution to the development of Magento 2 Async Bulk Operations UI is highly welcome.
The best possibility to provide any code is to open a pull request on GitHub.
Need Support?
If you encounter any problems or bugs, please create an issue on GitHub.
Please visit our store for more FREE / paid extensions OR contact us for customization / development services.


