tmonier / sylius-bulk-actions-plugin
Bulk actions for the Sylius 2 admin grids: enable/disable, taxons and channels for products, cancel orders, mark payments as paid, ship shipments, customer groups
Package info
github.com/thibmonier/sylius-bulk-actions-plugin
Type:sylius-plugin
pkg:composer/tmonier/sylius-bulk-actions-plugin
Requires
- php: ^8.2
- sylius/sylius: ^2.2
Requires (Dev)
- nyholm/psr7: ^1.8
- phpstan/phpstan: ^2.1
- phpstan/phpstan-doctrine: ^2.0
- phpstan/phpstan-webmozart-assert: ^2.0
- phpunit/phpunit: ^10.5
- sylius-labs/coding-standard: ^4.4
- sylius/test-application: ^2.0.0@alpha
- symfony/browser-kit: ^6.4 || ^7.4 || ^8.0
- symfony/css-selector: ^6.4 || ^7.4 || ^8.0
- symfony/debug-bundle: ^6.4 || ^7.4 || ^8.0
- symfony/dom-crawler: ^6.4 || ^7.4 || ^8.0
- symfony/dotenv: ^6.4 || ^7.4 || ^8.0
- symfony/http-client: ^6.4 || ^7.4 || ^8.0
- symfony/intl: ^6.4 || ^7.4 || ^8.0
- symfony/runtime: ^6.4 || ^7.4 || ^8.0
- symfony/web-profiler-bundle: ^6.4 || ^7.4 || ^8.0
- symfony/webpack-encore-bundle: ^2.2
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-11 07:36:56 UTC
README
Bulk actions for the Sylius 2 admin grids: products, orders, payments, shipments and customers.
The Sylius 2 admin grids can only delete in bulk. Disabling 40 products, cancelling a batch of unpaid orders, marking bank transfers as paid or shipping the morning's parcels still means opening every item. The Sylius 1 bulk plugins were never ported to Sylius 2.
This plugin adds a Bulk actions menu to the grids. Select rows, pick an action, confirm, and get a report of what was updated, what was skipped and why. It is free and open source.
Features
| Grid | Bulk actions |
|---|---|
| Products | Enable / disable; add to a taxon (optionally as main taxon) / remove from a taxon; add to / remove from channels |
| Product variants | Enable / disable |
| Orders | Cancel; mark as paid (completes the open payment) |
| Payments | Mark as paid (complete transition) |
| Shipments | Ship (without tracking code); ship with tracking codes: paste order number;tracking code lines to ship many orders, each with its own tracking code |
| Customers | Assign to a group / remove from their group; enable / disable the shop account |
How it works:
- Same behaviour as the single admin action. State changes go through the Sylius state
machine abstraction (
can()first, thenapply()), and the plugin dispatches the same resource events as the admin buttons (sylius.shipment.pre_ship/post_ship,sylius.payment.pre_complete/post_complete,sylius.order.pre_update/post_update,sylius.product.pre_update/post_update...). So the shipment confirmation e-mail is sent, the stock check before completing a payment runs, catalog promotions are refreshed and order cancellation still cancels payments, shipments and inventory holds. - Each item on its own. An item that cannot be changed is skipped with a reason (transition not allowed, already disabled, a variant has no price in the channel, guest without account...). An item that throws is failed and the other items are still processed.
- Clear report. A flash shows "Cancel: 18 updated, 2 skipped, 0 failed.", and a table below lists every skipped or failed item with its reason.
- Safe. The confirmation page is CSRF protected and only open to administrators. It shows the selected items and the action form (taxon picker, channels, customer group). A request is capped at 500 items by default (configurable), with a clear message beyond that. Changes are flushed in batches.
- Extensible. Implement
BulkActionInterfaceand your action shows up in the grid of its resource. Listen totmonier_bulk_actions.post_executeto get the report.
Also included: translations in English, French, German, Spanish, Italian, Dutch and Polish; Twig Hooks; no database table; MySQL, MariaDB and PostgreSQL support.
Screenshots
Screenshots go in docs/images/.
Requirements
| Version | |
|---|---|
| PHP | 8.2+ |
| Sylius | 2.2, 2.3 |
| Symfony | 6.4, 7.4, 8.0 |
Transitions are applied through Sylius\Abstraction\StateMachine\StateMachineInterface, so
both the Symfony Workflow adapter (the Sylius 2 default) and Winzou-mapped graphs work.
Installation
-
Require the package:
composer require tmonier/sylius-bulk-actions-plugin
-
Register the bundle in
config/bundles.php(Symfony Flex does it automatically once a recipe exists):Tmonier\SyliusBulkActionsPlugin\TmonierSyliusBulkActionsPlugin::class => ['all' => true],
-
Import the configuration in
config/packages/tmonier_sylius_bulk_actions.yaml:imports: - { resource: "@TmonierSyliusBulkActionsPlugin/config/config.yaml" }
-
Import the admin routes in
config/routes/tmonier_sylius_bulk_actions.yaml:tmonier_sylius_bulk_actions: resource: "@TmonierSyliusBulkActionsPlugin/config/routes.yaml"
There is no migration: the plugin has no table.
Configuration
# config/packages/tmonier_sylius_bulk_actions.yaml tmonier_sylius_bulk_actions: max_items: 500 # items per bulk request (default 500) batch_size: 50 # items changed between two flushes (default 50) actions: # every action is enabled by default order_cancel: { enabled: false } customer_disable_account: false
Action codes: product_enable, product_disable, product_add_taxon, product_remove_taxon,
product_add_channels, product_remove_channels, product_variant_enable,
product_variant_disable, order_cancel, order_payment_complete, payment_complete,
shipment_ship (also used by the Ship with tracking codes page), customer_assign_group,
customer_remove_group, customer_enable_account, customer_disable_account.
The menu is added to the grids sylius_admin_product, sylius_admin_product_variant,
sylius_admin_order, sylius_admin_payment, sylius_admin_shipment and
sylius_admin_customer as a bulk action of type tmonier_bulk_actions. To remove it from a
grid, or to show only some actions in a given order:
sylius_grid: grids: sylius_admin_customer: actions: bulk: tmonier_bulk_actions: enabled: false sylius_admin_order: actions: bulk: tmonier_bulk_actions: options: resource: sylius.order actions: [order_payment_complete]
The same bulk action type works on your own grids: set options.resource to the resource
alias of the grid (e.g. app.supplier).
Shipping with tracking codes
One tracking code for many parcels makes no sense, so the grid action ships without tracking code. To ship with tracking codes, click Ship with tracking codes on the shipments page and paste one line per shipment, e.g. a column copied from your carrier's export:
000000042;1Z999AA10123456784
000000043,1Z999AA10123456785
#000000044 COLISSIMO-6A123
The separator can be ;, , or a tab, and a leading # is ignored. Each line ships the next
shipment of the order that is ready to ship, with its own tracking code, and the customer gets
the shipment confirmation e-mail. Repeat the order number to ship an order with several
shipments. Unknown orders and orders with nothing to ship are listed in the report.
Writing your own action
use Tmonier\SyliusBulkActionsPlugin\Action\BulkActionInterface; use Tmonier\SyliusBulkActionsPlugin\Action\BulkActionResult; final class ArchiveSupplierAction implements BulkActionInterface { public function getCode(): string { return 'supplier_archive'; } public function getResource(): string { return 'app.supplier'; } // grid resource public function getLabel(): string { return 'app.ui.archive'; } // messages domain public function getIcon(): string { return 'tabler:archive'; } public function getFormType(): ?string { return null; } // or a form type class public function supports(object $item): bool { return $item instanceof Supplier; } public function execute(object $item, array $data): BulkActionResult { if ($item->isArchived()) { return BulkActionResult::skipped('app.bulk.already_archived'); // flashes domain } $item->archive(); return BulkActionResult::applied(); } }
With autoconfiguration the service is registered automatically. Otherwise tag it
tmonier_bulk_actions.action. Rules:
- never flush in
execute(): the processor flushes everybatch_sizeitems; - return
skipped()rather than throwing when the item cannot be changed; getFormType()returns a form type whose data is given toexecute()as$data;- to apply a state machine transition the way the admin does, use
TransitionApplierInterface::apply($subject, $graph, $transition, $resourceAlias, $eventName); to dispatch the resource events around a plain change, useResourceEventDispatcherInterface. The "post" event runs once the batch is flushed, throughBulkActionResult::applied($afterFlush).
After every run, tmonier_bulk_actions.post_execute is dispatched with a
BulkActionExecutedEvent (action, data, report with the outcome of every item).
Limitations
- The selection is the checked rows of the current grid page. Use the items per page selector (up to 100 in Sylius by default) and filters for larger batches. There is no "select all matching the filter" option.
- Processing is synchronous, in the admin request.
max_itemskeeps it bounded; for thousands of items use the Sylius API or a console command. - A database error during a flush fails its batch and stops the run: the remaining items are reported as failed. Changes already flushed are kept, there is no global rollback.
- Add to channels does not create prices: a product is skipped when one of its variants has no price in the target channel.
- Sylius Plus RBAC is not integrated: every administrator (
ROLE_ADMINISTRATION_ACCESS) can run the enabled bulk actions. Use theactionsconfiguration to turn some off.
Development
docker compose up -d mysql # or your own MySQL composer install (cd vendor/sylius/test-application && yarn install && yarn build) vendor/bin/console doctrine:database:create vendor/bin/console doctrine:migrations:migrate -n vendor/bin/console sylius:fixtures:load -n vendor/bin/phpunit --testsuite unit APP_ENV=test vendor/bin/phpunit --testsuite non-unit # needs a test database with fixtures vendor/bin/phpstan analyse vendor/bin/ecs check
License
MIT. See LICENSE.