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.

etc/events.xml
<?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.

Observer/FlagHighValueOrder.php
<?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:

Model/FaqSaver.php
<?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 $_eventPrefix also dispatch automatic events such as mageservices_faq_save_after and mageservices_faq_delete_after when saved through their resource model.

Useful Built-In Events

EventWhen it firesData
customer_register_successAfter storefront registrationcustomer, account_controller
sales_order_place_afterDuring order placement, before saveorder
checkout_submit_all_afterAfter the order is placed from checkoutorder, quote
sales_order_invoice_payWhen an invoice is paidinvoice
catalog_product_save_afterAfter a product is savedproduct
checkout_cart_product_add_afterAfter a product is added to the cartquote_item, product
controller_action_predispatchBefore every controller actioncontroller_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_predispatch unless 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.

Explore Magento Extension Development
MS
About the author

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.