The sales_channel.*.loaded events explained

In this text i will try to explain how the sales_channel.*.loaded events are created.

What are the sales_channel.*.loaded events?

The sales_channel.*.loaded events are events which are dynamically created/triggered after the non-sales_channel loaded.* events are ran (for example, after the product.loaded event is ran, a sales_channel.product.loaded event will be created too). They are being used internally as points where the calculations occur, for example, for the product prices.

So, you could say they are after-events of the *.loaded events, being triggered in the context of a sales channel, and filled with additional calculated information.

So, the reason this exists is actually giving context to the “normal” loaded events and filling them with context-specific data when needed.

When should I use which events?

You would probably want to use the NON-sales_channel loaded event if:

  • you need to preprocess the data of the entity before the calculations occur
  • you need to add some non-context-specific data on the entity, which will be available on all contexts (so included on api calls or in admin)

You would want to use the sales_channel.*.loaded event:

  • whenever you want to add additional data to the frontend and don’t need it anywhere else
  • or you need the frontend-specific internal calculations already included into the entity for post-processing

How are they created?

As an example, I will compare the product.loaded and its sales_channel.product.loaded event.


If you get a look at the product while triggering the product.loaded event, you will see that none of the prices were calculated. You only get the raw product entity data. The reason behind this is that the calculations themselves are happening afterwards in the sales_channel.product.loaded event.

The product.loaded event entity, getting the calculatedPrice

Another important thing to mention is that the entity you get back from the product.loaded event does not necessarily need to be of a specific type as the event is used in other contexts (admin, api, …), whereas the entity from the sales_channel.product.loaded event will always be the Shopware\Core\Content\Product\SalesChannel\SalesChannelProductEntity which in turn is an extension of our “normal” ProductEntity.

the same entity, but under the sales_channel.product.loaded event

Just for sakes of showing how the calculations for the products are done, let’s have a short look at the Shopware\Core\Content\Product\SalesChannel\SalesChannelProductSubscriber that calculates all of the prices on the same sales_channel.product.loaded event:

Now, let’s dig a bit deeper and see how the event is actually created.

If you have a look at the core Shopware\Core\System\SalesChannel\Entity\SalesChannelRepository::read() method, you will see both the EntityLoadedEvent trigger, and, afterwards the SalesChannelEntityLoaded event trigger.

EntityLoadedEvent and the SalesChannelEntityLoaded event

The way it dynamically creates the sales_channel.*.loaded events – the \Shopware\Core\System\SalesChannel\Entity\SalesChannelEntityLoadedEvent::getName() method:

The getName() method will be used while dispatching the SalesChannelEntityLoadedEvent.

So whenever this event is created (from the product.loaded/category.loaded and similar events), no matter which type of entity, it will be prefixed with the sales_channel.

TL;DR

So, to recap, the sales_channel.*.loaded events are:

  • a convenient way to dynamically add data to the entity that will be used on the frontend, without disturbing the entity in other contexts (admin/api)
  • whenever you need to do something with the calculations like prices, you will have all needed data already included

The dynamically created events I catched so far, that you can listen to (there are probably more):

  • sales_channel.product.loaded
  • sales_channel.language.loaded
  • sales_channel.category.loaded
  • sales_channel.currency.loaded
  • sales_channel.shipping_method.loaded
  • sales_channel.payment_method.loaded

Leave a Reply