Skip to content

Event Listeners

Bagisto announces what happens in the store with string events: dot-delimited names such as catalog.product.update.after, fired with Laravel's Event::dispatch(), mostly in before and after pairs around a create, update, delete or save. A package reacts to them from its own event service provider, so it never edits a core controller or repository. This page shows how to register a listener, then lists every event core dispatches with what a listener receives; events fired from Blade views are on View Render Events.

On this page

Register a Listener

Core fires a pair of events around each write, as CategoryController::store() in packages/Webkul/Admin/src/Http/Controllers/Catalog/CategoryController.php does:

php
Event::dispatch('catalog.category.create.before');

$category = $this->categoryRepository->create($data);

Event::dispatch('catalog.category.create.after', $category);

A package maps event names to listener methods in an event service provider of its own, in the same shape as Webkul\Admin\Providers\EventServiceProvider. This example sends every new order to an external ERP:

File: packages/Webkul/ErpSync/src/Providers/EventServiceProvider.php

php
<?php

namespace Webkul\ErpSync\Providers;

use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Webkul\ErpSync\Listeners\Order;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event handler mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        'checkout.order.save.after' => [
            [Order::class, 'afterCreated'],
        ],
    ];
}

File: packages/Webkul/ErpSync/src/Listeners/Order.php

php
<?php

namespace Webkul\ErpSync\Listeners;

use Webkul\ErpSync\Jobs\SendOrder;
use Webkul\Sales\Contracts\Order as OrderContract;

class Order
{
    /**
     * Queue a newly placed order for the ERP.
     */
    public function afterCreated(OrderContract $order): void
    {
        SendOrder::dispatch($order->id)->afterCommit();
    }
}

The listener only queues a job, once the order's database transaction has committed, so a slow or failing ERP never holds up checkout. The package's main service provider merges the job's configuration in register() and registers the event service provider from boot(), as AdminServiceProvider and ProductServiceProvider do:

File: packages/Webkul/ErpSync/src/Providers/ErpSyncServiceProvider.php

php
<?php

namespace Webkul\ErpSync\Providers;

use Illuminate\Support\ServiceProvider;

class ErpSyncServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     */
    public function register(): void
    {
        $this->mergeConfigFrom(dirname(__DIR__).'/Config/erp-sync.php', 'erp_sync');
    }

    /**
     * Bootstrap services.
     */
    public function boot(): void
    {
        $this->app->register(EventServiceProvider::class);
    }
}

Register ErpSyncServiceProvider in bootstrap/providers.php and the package namespace in composer.json, as in Package Development, then confirm the listener is attached:

bash
php artisan event:list --event=checkout.order.save.after

The command lists every listener on the event, core's included. For a listener built step by step in a package, see Events, Commands and Tests. Before you ship a listener, read Things to Watch: an exception in a checkout listener rolls the order back, and a listener that returns false stops core's listeners after it.

The SendOrder Job and Its Configuration

SendOrder is an ordinary queued job that loads the order through its repository and posts it, to the endpoint and token in Config/erp-sync.php, which register() above merges under erp_sync.

File: packages/Webkul/ErpSync/src/Config/erp-sync.php

php
<?php

return [
    'endpoint' => env('ERP_SYNC_ENDPOINT'),

    'token' => env('ERP_SYNC_TOKEN'),
];

File: packages/Webkul/ErpSync/src/Jobs/SendOrder.php

php
<?php

namespace Webkul\ErpSync\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
use Webkul\Sales\Repositories\OrderRepository;

class SendOrder implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /**
     * The number of times the job may be attempted.
     */
    public int $tries = 3;

    /**
     * Create a new job instance.
     */
    public function __construct(protected int $orderId) {}

    /**
     * Send the order to the ERP.
     */
    public function handle(OrderRepository $orderRepository): void
    {
        $order = $orderRepository->find($this->orderId);

        if (! $order) {
            return;
        }

        Http::withToken(config('erp_sync.token'))
            ->post(config('erp_sync.endpoint'), [
                'increment_id' => $order->increment_id,
                'grand_total' => $order->base_grand_total,
                'items' => $order->items->map(fn ($item) => [
                    'sku' => $item->sku,
                    'qty' => $item->qty_ordered,
                ])->all(),
            ])
            ->throw();
    }
}

What a Listener Receives

Names follow <domain>.<entity>.<action>.<before|after>. By convention a before event carries the record's id, or nothing when the record doesn't exist yet, and an after event carries the resulting model, except a delete, whose after event carries the id. Some names break the pattern (customer.after.login, data_transfer.imports.started, sales.invoice.send_duplicate_email), so take the exact name and payload from the tables.

Laravel passes the payload to a listener as arguments. A payload that isn't an array is wrapped in one, and an array payload is spread into one argument per value, with its keys dropped:

DispatchListener signature
Event::dispatch('cms.page.create.before')beforeCreate(), called with no arguments
Event::dispatch('catalog.category.create.after', $category)afterCreate($category)
Event::dispatch('checkout.order.save.before', [$data])beforeCreate(array $data)
Event::dispatch('sales.invoice.send_duplicate_email', ['invoice' => $invoice, 'duplicate_invoice_email' => $email])afterCreated($invoice, $duplicateInvoiceEmail = null)

An event dispatched without a payload calls its listeners with no arguments, so a required parameter throws ArgumentCountError; give it a default ($id = null) when one method handles both a create.before and an update.before. That's also why OrderRepository wraps $data in a second array for checkout.order.save.before. A few core events pass an unwrapped array (sales.invoice.save.before, sales.refund.save.before, sales.shipment.save.before, the RMA request create.before events, checkout.order.orderitem.save.before, section.reorder.before and section.media.upload.after), so their listeners receive the array's values as separate arguments.

Other Ways to Register

  • Event::listen() in boot(), with a closure or a [Listener::class, 'method'] pair, as Webkul\SocialShare\Providers\EventServiceProvider does.
  • A class name without a method, which calls the listener's handle(), as Webkul\Core\Providers\EventServiceProvider maps the repository events to Webkul\Core\Listeners\CleanCacheRepository.
  • From register() instead of boot(), as OmnibusServiceProvider registers its event service provider; both work.

Firing Events from Your Package

Give your package's own writes the same hooks, so other packages can extend yours the way yours extends core. Fire both halves, name them <package>.<entity>.<action>.<before|after>, and pass what core passes: nothing before a create, the id before an update or delete, the model after a create or update, and the id after a delete. Wrap an array payload so a listener receives it whole:

php
Event::dispatch('blog.post.create.before');

$post = $this->postRepository->create($data);

Event::dispatch('blog.post.create.after', $post);

A string name needs no event class for another package to import.

Available Events

Every string event the core packages dispatch, grouped by area, with what a listener receives. Two families aren't listed individually: every DataGrid dispatches datagrid.{grid_name}.{stage} events as it is built (see DataGrid), and every view_render_event() call in a Blade view dispatches an event of the same name (see View Render Events). Bagisto 2.4 dispatches the same events except catalog.product.price.reindex.before and .after and promotions.catalog_rule.reindex.before and .after, which are new in Bagisto 2.5.

Catalog

Dispatched by the admin catalog controllers in packages/Webkul/Admin/src/Http/Controllers/Catalog and by the price indexer. catalog.product.update.after is also fired by InvoiceItemRepository and ShipmentItemRepository after they change a product's stock.

EventFiredListener receives
catalog.attribute.create.afterAfter an attribute is createdthe attribute
catalog.attribute.create.beforeBefore an attribute is creatednothing
catalog.attribute.delete.afterAfter an attribute is deletedthe id
catalog.attribute.delete.beforeBefore an attribute is deletedthe id
catalog.attribute.update.afterAfter an attribute is updatedthe attribute
catalog.attribute.update.beforeBefore an attribute is updatedthe id
catalog.attribute_family.create.afterAfter an attribute family is createdthe attribute family
catalog.attribute_family.create.beforeBefore an attribute family is creatednothing
catalog.attribute_family.delete.afterAfter an attribute family is deletedthe id
catalog.attribute_family.delete.beforeBefore an attribute family is deletedthe id
catalog.attribute_family.update.afterAfter an attribute family is updatedthe attribute family
catalog.attribute_family.update.beforeBefore an attribute family is updatedthe id
catalog.categories.mass-update.afterAfter each category in a mass update is changedthe category
catalog.categories.mass-update.beforeBefore each category in a mass update is changedthe category id
catalog.category.create.afterAfter a category is createdthe category
catalog.category.create.beforeBefore a category is creatednothing
catalog.category.delete.afterAfter a category is deletedthe id
catalog.category.delete.beforeBefore a category is deletedthe id
catalog.category.update.afterAfter a category is updatedthe category
catalog.category.update.beforeBefore a category is updatedthe id
catalog.product.create.afterAfter a product is createdthe product
catalog.product.create.beforeBefore a product is creatednothing
catalog.product.delete.afterAfter a product is deletedthe id
catalog.product.delete.beforeBefore a product is deletedthe id
catalog.product.price.reindex.afterAfter the price indexer runs from indexer:indexthe reindexed product ids array; nothing after a full reindex
catalog.product.price.reindex.beforeBefore the price indexer runs from indexer:indexnothing
catalog.product.update.afterAfter a product is updatedthe product
catalog.product.update.beforeBefore a product is updatedthe id
products.datagrid.syncAfter a product mass action, to refresh the product gridtrue

Customers

Dispatched by the admin customer controllers, the storefront account, address, wishlist, compare, review and subscription controllers, the WebMCP controller, Webkul\Checkout\Cart when a cart item moves to the wishlist, and the social login controller for customer.after.login.

EventFiredListener receives
customer.addresses.create.afterAfter a customer address is createdthe address
customer.addresses.create.beforeBefore a customer address is creatednothing
customer.addresses.delete.afterAfter a customer address is deletedthe id
customer.addresses.delete.beforeBefore a customer address is deletedthe id
customer.addresses.update.afterAfter a customer address is updatedthe address
customer.addresses.update.beforeBefore a customer address is updatedthe id; nothing from the storefront API
customer.after.loginAfter a customer signs inthe customer
customer.after.logoutAfter a customer signs outthe customer id
customer.compare.create.afterAfter a product is added to the compare listthe compare item
customer.compare.create.beforeBefore a product is added to the compare listnothing
customer.compare.delete-all.afterAfter the compare list is clearednothing
customer.compare.delete-all.beforeBefore the compare list is clearednothing
customer.compare.delete.afterAfter a product is removed from the compare listthe product id
customer.compare.delete.beforeBefore a product is removed from the compare listthe product id
customer.create.afterAfter an admin creates a customer or a customer registers on the storefrontthe customer
customer.create.beforeBefore an admin creates a customernothing
customer.customer_group.create.afterAfter a customer group is createdthe customer group
customer.customer_group.create.beforeBefore a customer group is creatednothing
customer.customer_group.delete.afterAfter a customer group is deletedthe id
customer.customer_group.delete.beforeBefore a customer group is deletedthe id
customer.customer_group.update.afterAfter a customer group is updatedthe customer group
customer.customer_group.update.beforeBefore a customer group is updatedthe id
customer.delete.afterAfter a customer is deletedthe customer
customer.delete.beforeBefore a customer is deletedthe customer
customer.note.create.afterAfter a customer note is createdthe customer note
customer.note.create.beforeBefore a customer note is createdthe customer id
customer.password.update.afterAfter a customer changes or resets their passwordthe customer
customer.registration.afterAfter a customer registers, or an admin creates onethe customer
customer.registration.beforeBefore a customer registers, or an admin creates onenothing
customer.review.create.afterAfter a product review is createdthe review
customer.review.create.beforeBefore a product review is createdthe product id
customer.review.delete.afterAfter a product review is deletedthe id
customer.review.delete.beforeBefore a product review is deletedthe id
customer.review.update.afterAfter a product review is updatedthe review
customer.review.update.beforeBefore a product review is updatedthe id
customer.subscription.afterAfter a newsletter subscription is savedthe subscription
customer.subscription.beforeBefore a newsletter subscription is savednothing
customer.update.afterAfter a customer is updatedthe customer
customer.update.beforeBefore a customer is updatedthe id; nothing from the storefront account page
customer.wishlist.create.afterAfter a product is added to the wishlistthe wishlist
customer.wishlist.create.beforeBefore a product is added to the wishlistthe product id
customer.wishlist.delete-all.afterAfter the wishlist is clearednothing
customer.wishlist.delete-all.beforeBefore the wishlist is clearednothing
customer.wishlist.delete.afterAfter a wishlist item is removedthe wishlist item id
customer.wishlist.delete.beforeBefore a wishlist item is removedthe wishlist item id
customer.wishlist.move-to-cart.afterAfter a wishlist item is moved to the cartthe wishlist item id
customer.wishlist.move-to-cart.beforeBefore a wishlist item is moved to the cartthe wishlist item id
customer.wishlist.update.afterAfter a cart item moved to the wishlist updates an existing wishlist itemthe wishlist item
customer.wishlist.update.beforeBefore a cart item moved to the wishlist updates an existing wishlist itemthe product id

GDPR Requests

Dispatched by the storefront and admin GDPRController.

EventFiredListener receives
customer.account.gdpr-request.create.afterAfter a customer submits a GDPR requestthe GDPR request
customer.account.gdpr-request.create.beforeBefore a customer submits a GDPR requestnothing
customer.account.gdpr-request.update.afterAfter a GDPR request is revoked by the customer or updated by an adminthe GDPR request
customer.account.gdpr-request.update.beforeBefore a customer revokes a GDPR requestnothing
customer.gdpr-request.create.afterAfter a customer submits a GDPR request (fired right after the one above)the GDPR request
customer.gdpr-request.update.afterAfter a customer revokes a GDPR requestthe GDPR request
customer.gdpr-request.update.beforeBefore an admin updates a GDPR requestnothing

Cart and Checkout

Dispatched by Webkul\Checkout\Cart, Webkul\Sales\Repositories\OrderRepository and the storefront OnepageController.

EventFiredListener receives
checkout.cart.add.afterAfter a product is added to the cartthe cart
checkout.cart.add.beforeBefore a product is added to the cartthe product id
checkout.cart.calculate.items.tax.afterAfter tax is calculated on the cart itemsthe cart
checkout.cart.calculate.items.tax.beforeBefore tax is calculated on the cart itemsthe cart
checkout.cart.calculate.shipping.tax.afterAfter tax is calculated on shippingthe cart
checkout.cart.calculate.shipping.tax.beforeBefore tax is calculated on shippingthe cart
checkout.cart.collect.totals.afterAfter the cart totals are collectedthe cart
checkout.cart.collect.totals.beforeBefore the cart totals are collectedthe cart
checkout.cart.delete.afterAfter a cart item is removedthe cart item id
checkout.cart.delete.beforeBefore a cart item is removedthe cart item id
checkout.cart.update.afterAfter a cart item quantity is updatedthe cart item
checkout.cart.update.beforeBefore a cart item quantity is updatedthe cart item
checkout.load.indexWhen the one-page checkout page loadsnothing
checkout.order.orderitem.save.afterAfter an order item is savedthe order item
checkout.order.orderitem.save.beforeBefore an order item is savedthe item data array, one argument per value
checkout.order.save.afterAfter an order and its items are created, before the transaction commitsthe order
checkout.order.save.beforeBefore an order is created, inside the order transactionthe order data array

Sales

The save, cancel and update-status events are dispatched by the Sales repositories, so they fire wherever an order, invoice, shipment or refund goes through them. The comment and duplicate email events come from the admin controllers, and the RMA RequestController also fires sales.order.cancel.after.

EventFiredListener receives
sales.invoice.save.afterAfter an invoice is savedthe invoice
sales.invoice.save.beforeBefore an invoice is savedthe request data array, one argument per value
sales.invoice.send_duplicate_emailWhen an admin sends an invoice email againthe invoice, then the email address
sales.order.cancel.afterAfter an order is canceledthe order
sales.order.cancel.beforeBefore an order is canceledthe order
sales.order.comment.create.afterAfter an admin adds an order commentthe order comment
sales.order.comment.create.beforeBefore an admin adds an order commentnothing
sales.order.update-status.afterAfter an order status is recalculatedthe order
sales.order.update-status.beforeBefore an order status is recalculatedthe order
sales.refund.save.afterAfter a refund is savedthe refund
sales.refund.save.beforeBefore a refund is savedthe request data array, one argument per value
sales.shipment.save.afterAfter a shipment is savedthe shipment
sales.shipment.save.beforeBefore a shipment is savedthe request data array, one argument per value

Returns (RMA)

Dispatched by the admin controllers in packages/Webkul/Admin/src/Http/Controllers/Sales/RMA and the storefront RMAController.

EventFiredListener receives
customer.rma.request.create.afterAfter a customer creates a return requestthe return request
customer.rma.request.create.beforeBefore a customer creates a return requestthe request data array, one argument per value
customer.rma.request.update.afterAfter a customer updates a return requestthe return request
customer.rma.request.update.beforeBefore a customer updates a return request (reopen, cancel or close)the id
sales.rma.custom-field.create.afterAfter an RMA custom field is createdthe RMA custom field
sales.rma.custom-field.create.beforeBefore an RMA custom field is creatednothing
sales.rma.custom-field.delete.afterAfter an RMA custom field is deletedthe id
sales.rma.custom-field.delete.beforeBefore an RMA custom field is deletedthe id
sales.rma.custom-field.update.afterAfter an RMA custom field is updatedthe RMA custom field
sales.rma.custom-field.update.beforeBefore an RMA custom field is updatedthe id
sales.rma.reason.create.afterAfter an RMA reason is createdthe RMA reason
sales.rma.reason.create.beforeBefore an RMA reason is creatednothing
sales.rma.reason.delete.afterAfter an RMA reason is deletedthe id
sales.rma.reason.delete.beforeBefore an RMA reason is deletedthe id
sales.rma.reason.update.afterAfter an RMA reason is updatedthe RMA reason
sales.rma.reason.update.beforeBefore an RMA reason is updatedthe id
sales.rma.request.create.afterAfter an admin creates a return requestthe return request
sales.rma.request.create.beforeBefore an admin creates a return requestthe request data array, one argument per value
sales.rma.rma-status.create.afterAfter an RMA status is createdthe RMA status
sales.rma.rma-status.create.beforeBefore an RMA status is creatednothing
sales.rma.rma-status.delete.afterAfter an RMA status is deletedthe id
sales.rma.rma-status.delete.beforeBefore an RMA status is deletedthe id
sales.rma.rma-status.update.afterAfter an RMA status is updatedthe RMA status
sales.rma.rma-status.update.beforeBefore an RMA status is updatedthe id
sales.rma.rules.create.afterAfter an RMA rule is createdthe RMA rule
sales.rma.rules.create.beforeBefore an RMA rule is creatednothing
sales.rma.rules.delete.afterAfter an RMA rule is deletedthe id
sales.rma.rules.delete.beforeBefore an RMA rule is deletedthe id
sales.rma.rules.update.afterAfter an RMA rule is updatedthe RMA rule
sales.rma.rules.update.beforeBefore an RMA rule is updatedthe id

Promotions

Dispatched by the admin promotion controllers; the reindex pair by the UpdateCreateCatalogRuleIndex and DeleteCatalogRuleIndex jobs.

EventFiredListener receives
cart_rules.coupons.delete.afterAfter a cart rule coupon is deletedthe coupon
cart_rules.coupons.delete.beforeBefore a cart rule coupon is deletedthe coupon
promotions.cart_rule.create.afterAfter a cart rule is createdthe cart rule
promotions.cart_rule.create.beforeBefore a cart rule is creatednothing
promotions.cart_rule.delete.afterAfter a cart rule is deletedthe id
promotions.cart_rule.delete.beforeBefore a cart rule is deletedthe id
promotions.cart_rule.update.afterAfter a cart rule is updatedthe cart rule
promotions.cart_rule.update.beforeBefore a cart rule is updatedthe id
promotions.catalog_rule.create.afterAfter a catalog rule is createdthe catalog rule
promotions.catalog_rule.create.beforeBefore a catalog rule is creatednothing
promotions.catalog_rule.delete.afterAfter a catalog rule is deletedthe id
promotions.catalog_rule.delete.beforeBefore a catalog rule is deletedthe id
promotions.catalog_rule.reindex.afterAfter prices are reindexed for a saved or deleted catalog rulethe product ids array
promotions.catalog_rule.reindex.beforeBefore prices are reindexed for a saved or deleted catalog rulethe product ids array
promotions.catalog_rule.update.afterAfter a catalog rule is updatedthe catalog rule
promotions.catalog_rule.update.beforeBefore a catalog rule is updatedthe id

Marketing and SEO

Dispatched by the admin marketing controllers. The URL rewrite create and delete events are also fired by Webkul\Marketing\Listeners\Category, Product and Page, which maintain the rewrites of categories, products and CMS pages.

EventFiredListener receives
marketing.campaigns.create.afterAfter a campaign is createdthe campaign
marketing.campaigns.create.beforeBefore a campaign is creatednothing
marketing.campaigns.delete.afterAfter a campaign is deletedthe id
marketing.campaigns.delete.beforeBefore a campaign is deletedthe id
marketing.campaigns.update.afterAfter a campaign is updatedthe campaign
marketing.campaigns.update.beforeBefore a campaign is updatedthe id
marketing.events.create.afterAfter a marketing event is createdthe marketing event
marketing.events.create.beforeBefore a marketing event is creatednothing
marketing.events.delete.afterAfter a marketing event is deletedthe id
marketing.events.delete.beforeBefore a marketing event is deletedthe id
marketing.events.update.afterAfter a marketing event is updatedthe marketing event
marketing.events.update.beforeBefore a marketing event is updatedthe id
marketing.search_seo.search_synonyms.create.afterAfter a search synonym is createdthe search synonym
marketing.search_seo.search_synonyms.create.beforeBefore a search synonym is creatednothing
marketing.search_seo.search_synonyms.delete.afterAfter a search synonym is deletedthe id
marketing.search_seo.search_synonyms.delete.beforeBefore a search synonym is deletedthe id
marketing.search_seo.search_synonyms.update.afterAfter a search synonym is updatedthe search synonym
marketing.search_seo.search_synonyms.update.beforeBefore a search synonym is updatedthe id
marketing.search_seo.search_terms.create.afterAfter a search term is createdthe search term
marketing.search_seo.search_terms.create.beforeBefore a search term is creatednothing
marketing.search_seo.search_terms.delete.afterAfter a search term is deletedthe id
marketing.search_seo.search_terms.delete.beforeBefore a search term is deletedthe id
marketing.search_seo.search_terms.update.afterAfter a search term is updatedthe search term
marketing.search_seo.search_terms.update.beforeBefore a search term is updatedthe id
marketing.search_seo.sitemap.create.afterAfter a sitemap is createdthe sitemap
marketing.search_seo.sitemap.create.beforeBefore a sitemap is creatednothing
marketing.search_seo.sitemap.delete.afterAfter a sitemap is deletedthe id
marketing.search_seo.sitemap.delete.beforeBefore a sitemap is deletedthe id
marketing.search_seo.sitemap.update.afterAfter a sitemap is updatedthe sitemap
marketing.search_seo.sitemap.update.beforeBefore a sitemap is updatedthe id
marketing.search_seo.url_rewrites.create.afterAfter a URL rewrite is createdthe URL rewrite
marketing.search_seo.url_rewrites.create.beforeBefore a URL rewrite is creatednothing
marketing.search_seo.url_rewrites.delete.afterAfter a URL rewrite is deletedthe id
marketing.search_seo.url_rewrites.delete.beforeBefore a URL rewrite is deletedthe id
marketing.search_seo.url_rewrites.update.afterAfter a URL rewrite is updatedthe URL rewrite
marketing.search_seo.url_rewrites.update.beforeBefore a URL rewrite is updatedthe id
marketing.templates.create.afterAfter an email template is createdthe email template
marketing.templates.create.beforeBefore an email template is creatednothing
marketing.templates.delete.afterAfter an email template is deletedthe id
marketing.templates.delete.beforeBefore an email template is deletedthe id
marketing.templates.update.afterAfter an email template is updatedthe email template
marketing.templates.update.beforeBefore an email template is updatedthe id

CMS

Dispatched by the admin PageController.

EventFiredListener receives
cms.page.create.afterAfter a CMS page is createdthe CMS page
cms.page.create.beforeBefore a CMS page is creatednothing
cms.page.delete.afterAfter a CMS page is deletedthe id
cms.page.delete.beforeBefore a CMS page is deletedthe id
cms.page.update.afterAfter a CMS page is updatedthe CMS page
cms.page.update.beforeBefore a CMS page is updatedthe id

Settings

Dispatched by the admin settings controllers. core.currency.delete.* and core.locale.delete.* come from CurrencyRepository and LocaleRepository, core.configuration.save.* from CoreConfigRepository, and activating a theme also fires core.channel.update.before and .after from ThemeController.

EventFiredListener receives
admin.password.update.afterAfter an admin password is changedthe admin
core.channel.create.afterAfter a channel is createdthe channel
core.channel.create.beforeBefore a channel is creatednothing
core.channel.delete.afterAfter a channel is deletedthe id
core.channel.delete.beforeBefore a channel is deletedthe id
core.channel.update.afterAfter a channel is updatedthe channel
core.channel.update.beforeBefore a channel is updatedthe id
core.configuration.save.afterAfter configuration values are savednothing
core.configuration.save.beforeBefore configuration values are savednothing
core.currency.create.afterAfter a currency is createdthe currency
core.currency.create.beforeBefore a currency is creatednothing
core.currency.delete.afterAfter a currency is deletedthe id
core.currency.delete.beforeBefore a currency is deletedthe id
core.currency.update.afterAfter a currency is updatedthe currency
core.currency.update.beforeBefore a currency is updatedthe id
core.exchange_rate.create.afterAfter an exchange rate is createdthe exchange rate
core.exchange_rate.create.beforeBefore an exchange rate is creatednothing
core.exchange_rate.delete.afterAfter an exchange rate is deletedthe id
core.exchange_rate.delete.beforeBefore an exchange rate is deletedthe id
core.exchange_rate.update.afterAfter an exchange rate is updatedthe exchange rate
core.exchange_rate.update.beforeBefore an exchange rate is updatedthe id
core.locale.create.afterAfter a locale is createdthe locale
core.locale.create.beforeBefore a locale is creatednothing
core.locale.delete.afterAfter a locale is deletedthe id
core.locale.delete.beforeBefore a locale is deletedthe id
core.locale.update.afterAfter a locale is updatedthe locale
core.locale.update.beforeBefore a locale is updatedthe id
inventory.inventory_source.create.afterAfter an inventory source is createdthe inventory source
inventory.inventory_source.create.beforeBefore an inventory source is creatednothing
inventory.inventory_source.delete.afterAfter an inventory source is deletedthe id
inventory.inventory_source.delete.beforeBefore an inventory source is deletedthe id
inventory.inventory_source.update.afterAfter an inventory source is updatedthe inventory source
inventory.inventory_source.update.beforeBefore an inventory source is updatedthe id
tax.category.create.afterAfter a tax category is createdthe tax category
tax.category.create.beforeBefore a tax category is creatednothing
tax.category.delete.afterAfter a tax category is deletedthe id
tax.category.delete.beforeBefore a tax category is deletedthe id
tax.category.update.afterAfter a tax category is updatedthe tax category
tax.category.update.beforeBefore a tax category is updatedthe id
tax.rate.create.afterAfter a tax rate is createdthe tax rate
tax.rate.create.beforeBefore a tax rate is creatednothing
tax.rate.delete.afterAfter a tax rate is deletedthe id
tax.rate.delete.beforeBefore a tax rate is deletedthe id
tax.rate.update.afterAfter a tax rate is updatedthe tax rate
tax.rate.update.beforeBefore a tax rate is updatedthe id
user.admin.create.afterAfter an admin user is createdthe admin
user.admin.create.beforeBefore an admin user is creatednothing
user.admin.delete.afterAfter an admin user is deletedthe id
user.admin.delete.beforeBefore an admin user is deletedthe id
user.admin.update.afterAfter an admin user is updatedthe admin
user.admin.update.beforeBefore an admin user is updatedthe id
user.role.create.afterAfter a role is createdthe role
user.role.create.beforeBefore a role is creatednothing
user.role.delete.afterAfter a role is deletedthe id
user.role.delete.beforeBefore a role is deletedthe id
user.role.update.afterAfter a role is updatedthe role
user.role.update.beforeBefore a role is updatedthe id

Appearance

Dispatched by the admin SectionController and ThemeController.

EventFiredListener receives
appearance.theme.activate.afterAfter a theme is activated on a channelthe channel
appearance.theme.activate.beforeBefore a theme is activated on a channelthe channel id
section.create.afterAfter a theme section is createdthe section
section.create.beforeBefore a theme section is creatednothing
section.delete.afterAfter a theme section is deletedthe id
section.delete.beforeBefore a theme section is deletedthe id
section.draft.discard.afterAfter a section draft is discardedthe section
section.draft.discard.beforeBefore a section draft is discardedthe id
section.draft.save.afterAfter a section draft is savedthe section
section.draft.save.beforeBefore a section draft is savedthe id
section.media.upload.afterAfter media is uploaded to a sectionthe stored path, then the media type (image or video)
section.media.upload.beforeBefore media is uploaded to a sectionthe section id
section.reorder.afterAfter sections are reorderedthe sections
section.reorder.beforeBefore sections are reorderedeach section id as a separate argument
section.update.afterAfter a theme section is updatedthe section
section.update.beforeBefore a theme section is updatedthe id

DataGrid Saved Filters

Dispatched by the admin SavedFilterController.

EventFiredListener receives
datagrid.saved_filter.create.afterAfter a saved filter is createdthe saved filter
datagrid.saved_filter.create.beforeBefore a saved filter is creatednothing
datagrid.saved_filter.delete.afterAfter a saved filter is deletedthe id
datagrid.saved_filter.delete.beforeBefore a saved filter is deletedthe id
datagrid.saved_filter.update.afterAfter a saved filter is updatedthe saved filter
datagrid.saved_filter.update.beforeBefore a saved filter is updatedthe id

Data Transfer

create and update come from the admin ImportController, validate from AbstractImporter, and started, linking, indexing and completed from Webkul\DataTransfer\Helpers\Import. The batch events are dispatched by each importer's own importBatch(), linkBatch() and indexBatch(), so a custom importer fires them only if it dispatches them itself.

EventFiredListener receives
data_transfer.imports.batch.import.afterAfter an importer writes a batchthe import batch
data_transfer.imports.batch.import.beforeBefore an importer writes a batchthe import batch
data_transfer.imports.batch.indexing.afterAfter the product importer indexes a batchthe import batch
data_transfer.imports.batch.indexing.beforeBefore the product importer indexes a batchthe import batch
data_transfer.imports.batch.linking.afterAfter the product importer links a batchthe import batch
data_transfer.imports.batch.linking.beforeBefore the product importer links a batchthe import batch
data_transfer.imports.completedWhen an import completesthe import
data_transfer.imports.create.afterAfter an import is createdthe import
data_transfer.imports.create.beforeBefore an import is creatednothing
data_transfer.imports.indexingWhen an import enters the indexing stagethe import
data_transfer.imports.linkingWhen an import enters the linking stagethe import
data_transfer.imports.startedWhen an import starts processingthe import
data_transfer.imports.update.afterAfter an import is updatedthe import
data_transfer.imports.update.beforeBefore an import is updatednothing
data_transfer.imports.validate.afterAfter an import file is validatedthe import
data_transfer.imports.validate.beforeBefore an import file is validatedthe import

Booking Products

Dispatched by BookingRepository and BookingProductEventTicketRepository.

EventFiredListener receives
booking_product.booking.event-ticket.save.afterAfter event tickets are saved on a booking productthe saved tickets
booking_product.booking.event-ticket.save.beforeBefore event tickets are saved on a booking productthe ticket data, then the booking product
booking_product.booking.save.afterAfter a booking is savedthe booking
booking_product.booking.save.beforeBefore a booking is savedthe order item

Installer

Dispatched by the bagisto:install command and the web installer's CanInstall middleware.

EventFiredListener receives
bagisto.installedAfter installation completes, from the installer command or web installernothing

Things to Watch

  • An exception in a listener fails the action. checkout.order.save.before and checkout.order.save.after run inside the order's database transaction in OrderRepository::createOrderIfNotThenRetry(): an exception rolls the order back, and the repository tries again up to sales.order_settings.order_creation.max_retry_attempts times. Core's email listeners catch their own exceptions and report() them. A listener that returns false also stops the listeners after it, core's included, so return nothing from a listener method.
  • Change a core listener by binding a subclass. A listener mapped by class name is resolved from the container each time its event fires, so binding Webkul\Shop\Listeners\Order to your subclass in your provider's register() changes the customer's order email that its afterCreated() sends. Event::forget() is no substitute: it removes every listener of the event, other packages' included.
  • Queue slow work, after commit. A job dispatched inside a transaction can be picked up before the transaction commits and find no record. ->afterCommit() holds it back; with QUEUE_CONNECTION=sync the job still runs inside the request, once the transaction has committed.
  • The dispatch site decides whether an event fires, and with what. The catalog, customer, marketing and settings events are dispatched by the Admin and Shop controllers, so a record written through a repository elsewhere, by an importer, an API package or your own code, fires none of them; the sales events are the main exception, dispatched by the Sales repositories. Each table says where its events come from. The payload differs between sites too: customer.update.before carries the customer id from the admin but nothing from the storefront account page, so accept an optional argument where a table lists more than one shape.
  • A misspelled event name fails silently. Copy the name from the table and check it with php artisan event:list.
  • Repositories also fire class events. Every Bagisto repository extends Prettus's BaseRepository, whose create(), update(), updateOrCreate(), delete() and deleteWhere() fire Prettus\Repository\Events\RepositoryEntityCreated, RepositoryEntityUpdated and RepositoryEntityDeleted, unless a repository overrides the method without calling the parent (ProductRepository::create() hands the create to the product type). They don't fire for insert(), upsert() or query builder updates.

Released under the MIT License.