Magento dispatches events at important moments — a product is saved, a customer registers, an order is placed. Observers are classes that run when an event fires. They are the cleanest way to react to something without changing the code that caused it.
Step 1: Register the Observer in events.xml
Place events.xml in etc/ to listen in every area, or in etc/frontend/, etc/adminhtml/, etc/webapi_rest/ or etc/crontab/ to limit it. Here we listen for orders being placed, in all areas, because orders can come from the storefront, the admin or the API.
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
<event name="sales_order_place_after">
<observer name="mageservices_flag_high_value_order"
instance="MageServices\Sales\Observer\FlagHighValueOrder"/>
</event>
</config>
Step 2: Write the Observer
Observers implement ObserverInterface and receive an Observer object. Data passed with the event is available through $observer->getEvent(). This observer adds an order comment when the order total is above a threshold, so the warehouse team can check it.
<?php
declare(strict_types=1);
namespace MageServices\Sales\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Sales\Api\Data\OrderInterface;
use Magento\Sales\Model\Order;
class FlagHighValueOrder implements ObserverInterface
{
private const THRESHOLD = 1000.0;
public function execute(Observer $observer): void
{
/** @var Order|null $order */
$order = $observer->getEvent()->getData('order');
if (!$order instanceof OrderInterface) {
return;
}
if ((float) $order->getBaseGrandTotal() < self::THRESHOLD) {
return;
}
$order->addCommentToStatusHistory(
(string) __('High-value order: please verify before dispatch.')
);
}
}
sales_order_place_after is dispatched inside the order placement process before the order is saved, so the comment is saved together with the order. For work that must happen after the order is committed — such as sending it to an ERP — use checkout_submit_all_after or, better, publish a message to a queue.
Step 3: Dispatch Your Own Event
Dispatching events from your own modules lets other developers extend them without plugins. Inject Magento\Framework\Event\ManagerInterface and pass data as an array:
<?php
declare(strict_types=1);
namespace MageServices\Faq\Model;
use Magento\Framework\Event\ManagerInterface as EventManager;
use MageServices\Faq\Model\ResourceModel\Faq as FaqResource;
class FaqSaver
{
public function __construct(
private readonly FaqResource $faqResource,
private readonly EventManager $eventManager
) {
}
public function save(Faq $faq): void
{
$this->faqResource->save($faq);
$this->eventManager->dispatch('mageservices_faq_save_after', ['faq' => $faq]);
}
}
Models that set$_eventPrefixalso dispatch automatic events such asmageservices_faq_save_afterandmageservices_faq_delete_afterwhen saved through their resource model.
Useful Built-In Events
| Event | When it fires | Data |
|---|---|---|
customer_register_success | After storefront registration | customer, account_controller |
sales_order_place_after | During order placement, before save | order |
checkout_submit_all_after | After the order is placed from checkout | order, quote |
sales_order_invoice_pay | When an invoice is paid | invoice |
catalog_product_save_after | After a product is saved | product |
checkout_cart_product_add_after | After a product is added to the cart | quote_item, product |
controller_action_predispatch | Before every controller action | controller_action, request |
Observer Best Practices
- Keep observers small. Move logic into a service class you can test.
- Do not throw exceptions unless you intend to stop the whole process — an exception in a checkout observer stops the order.
- Avoid slow work such as API calls in observers on customer-facing requests. Publish a message to a queue and process it with a consumer instead.
- Avoid observers on very frequent events such as
controller_action_predispatchunless necessary; they run on every request. - To disable a core or third-party observer, redeclare it with
disabled="true"under the same event and observer name.
Testing Observers
Because observers receive an Observer object, they are easy to unit-test: create an Event with the data you expect, wrap it in an Observer, call execute() and assert on the result. Keeping business logic in a separate service makes the observer itself almost trivial and the service fully testable without Magento's event system.
Frequently Asked Questions
Where can I find the list of Magento events?
Search the codebase for ->dispatch( calls, or look for _eventPrefix in models, which generate save, load and delete events automatically.
Why is my observer not running?
Check the event name, the area of your events.xml, that the module is enabled, and flush the config cache.
Can an observer change data?
Yes, if the event passes objects, but plugins are better suited to changing method arguments and results.
Need help with this on your store?
Our Magento engineers can implement it for you, review your code or take on the whole project.
Written by the Magento Services engineering team — Magento 2, Adobe Commerce and Hyvä specialists since 2014. We write about problems we solve on real client stores.