Skip to content

Magento Observers and Events — Event Dispatching and Custom Events

DodaTech Updated 2026-06-27 14 min read

In this tutorial, you'll learn how Magento's event system works with observers, how to react to core events like order placement, and how to dispatch custom events from your own modules.

What You'll Learn

  • How Magento's event-driven architecture enables loosely coupled extensions
  • How to declare observers in events.xml for frontend, adminhtml, and global areas
  • How to create observer classes that react to events
  • How to dispatch custom events with data
  • Which core events are most useful for common customizations
  • Best practices for event naming and performance

Why It Matters

The event system is Magento's second major extension mechanism alongside plugins. While plugins intercept method calls, events let you react to actions that happen across the system. When a customer places an order, adds a product to the cart, or logs in, Magento fires events. You can write an observer that listens for any of these events and executes custom code. This is essential for tasks like sending custom emails, updating external systems, applying custom logic after checkout, or integrating with third-party APIs. Events make your code decoupled — your observer does not need to modify the core sales module to react to an order being placed.

Real-World Use

An online electronics store needs to send order data to their warehouse management system (WMS) every time an order is placed. The WMS needs the order items, quantities, and shipping address in XML format. Instead of modifying the core checkout process, you create an observer on the sales_order_place_after event. The observer collects the order data, transforms it to XML, and sends it to the WMS endpoint. If the WMS changes its API, you only update the observer. The checkout code remains untouched. If the warehouse is offline, the observer logs the failure and retries later without affecting the customer's checkout experience.

Learning Path

flowchart LR
  A["25: Module Structure"] --> B["26: Dependency Injection"]
  B --> C["27: Plugins"]
  C --> D["28: Observers and Events
You are here"]:::current classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px

What Is the Event System

Magento's event system follows the observer design pattern. When something important happens in the system — a product is saved, an order is placed, a customer logs in — the system dispatches an event with associated data. Any number of observers can listen for that event and execute code in response.

The key advantage: loose coupling. The code that dispatches the event does not know which observers will react. Observers can be added or removed without changing the code that fires the event. This allows multiple extensions to react to the same action without conflicting.

Event Flow

Product Save Action
  |
  v
Event Dispatched: catalog_product_save_after
  |
  +---> Observer 1: Update search index
  +---> Observer 2: Send notification
  +---> Observer 3: Sync to ERP
  |
  v
Original action continues

The event is dispatched synchronously. All observers run before the next line of code executes.

events.xml Structure

Events are declared in events.xml files. Like di.xml, events.xml can be placed in multiple area directories:

Location Scope
etc/events.xml Global (all areas)
etc/frontend/events.xml Storefront only
etc/adminhtml/events.xml Admin panel only
etc/webapi_rest/events.xml REST API only
etc/<a href="/apis/graphql/">Graphql</a>/events.xml GraphQL only

events.xml Structure

<?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="vendor_module_order_observer"
                  instance="Vendor\Module\Observer\OrderPlace"
                  shared="false"/>
    </event>
</config>
Attribute Purpose
event name The unique event identifier
observer name Unique observer identifier within this event
instance Fully qualified observer class name
shared Whether to reuse the observer instance (default true)

Observer Classes

An observer class implements \Magento\Framework\Event\ObserverInterface and defines an execute method.

<?php
namespace Vendor\Module\Observer;

use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Event\Observer;
use Psr\Log\LoggerInterface;

class OrderPlace implements ObserverInterface
{
    private $logger;

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    public function execute(Observer $observer)
    {
        $order = $observer->getEvent()->getOrder();
        $this->logger->info('Order placed: ' . $order->getIncrementId());

        // Custom logic: send to external system, update inventory, etc.
    }
}

Accessing Event Data

The Observer object provides access to the event data:

$event = $observer->getEvent();

// Get specific data passed with the event
$order = $event->getOrder();
$product = $event->getProduct();
$quote = $event->getQuote();
$customer = $event->getCustomer();

// Get all data as an array
$data = $event->getData();

The available getter methods depend on the event. Each event passes specific objects. You can also access data generically:

$order = $observer->getData('order');
// or
$order = $observer->getEvent()->getData('order');

Core Events

Magento fires hundreds of events. Here are the most useful ones for common customizations:

Checkout Events

Event When Data
checkout_cart_add_product_complete After product added to cart product, request, quote_item
checkout_cart_update_item_complete After cart item quantity updated item, info
checkout_cart_product_add_after After add to cart (lower level) quote_item, product
checkout_onepage_controller_success_action After order placed on frontend order_ids
checkout_submit_all_after After checkout submission quote, order

Sales Events

Event When Data
sales_order_place_after After order is placed order
sales_order_save_after After order is saved order
sales_order_invoice_register After invoice is created invoice, order
sales_order_shipment_save_after After shipment is saved shipment, order
sales_order_creditmemo_save_after After credit memo is saved creditmemo, order

Customer Events

Event When Data
customer_login After customer logs in customer
customer_logout After customer logs out customer
customer_register_success After customer registration customer
customer_address_save_after After address is saved customer_address, customer

Catalog Events

Event When Data
catalog_product_save_after After product is saved product
catalog_product_delete_after After product is deleted product
catalog_category_save_after After category is saved category
catalog_product_import_before Before product import <a href="/design-patterns/adapter/">Adapter</a>
catalog_product_import_after After product import adapter

Admin Events

Event When Data
admin_user_login_success After admin login user
admin_system_config_changed_section_ After config section saved (depends on section)
controller_action_predispatch_adminhtml Before any admin action controller_action

Controller Events

Event When Data
controller_action_predispatch Before any controller action controller_action
controller_action_postdispatch After any controller action controller_action

Example: React to Add to Cart

Create an observer that logs every add-to-cart action and sends data to an analytics service:

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="checkout_cart_add_product_complete">
        <observer name="vendor_module_add_to_cart"
                  instance="Vendor\Module\Observer\AddToCart"
                  shared="false"/>
    </event>
</config>

Observer Class

<?php
namespace Vendor\Module\Observer;

use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Event\Observer;
use Psr\Log\LoggerInterface;

class AddToCart implements ObserverInterface
{
    private $logger;

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }

    public function execute(Observer $observer)
    {
        $product = $observer->getEvent()->getProduct();
        $quoteItem = $observer->getEvent()->getQuoteItem();

        $this->logger->info('Product added to cart', [
            'sku' => $product->getSku(),
            'name' => $product->getName(),
            'qty' => $quoteItem->getQty(),
            'price' => $quoteItem->getPrice()
        ]);
    }
}

Example: Order Confirmation to External API

<?php
namespace Vendor\Module\Observer;

use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Event\Observer;
use Magento\Framework\HTTP\Client\Curl;
use Psr\Log\LoggerInterface;

class OrderSync implements ObserverInterface
{
    private $curl;
    private $logger;

    public function __construct(Curl $curl, LoggerInterface $logger)
    {
        $this->curl = $curl;
        $this->logger = $logger;
    }

    public function execute(Observer $observer)
    {
        $order = $observer->getEvent()->getOrder();
        $data = [
            'order_id' => $order->getIncrementId(),
            'total' => $order->getGrandTotal(),
            'items' => []
        ];

        foreach ($order->getAllItems() as $item) {
            $data['items'][] = [
                'sku' => $item->getSku(),
                'qty' => $item->getQtyOrdered(),
                'price' => $item->getPrice()
            ];
        }

        try {
            $this->curl->post('https://wms.example.com/api/orders', json_encode($data));
        } catch (\Exception $e) {
            $this->logger->error('WMS sync failed: ' . $e->getMessage());
        }
    }
}

Dispatching Custom Events

You can dispatch your own events from any PHP class. This allows other modules to react to actions in your module.

<?php
namespace Vendor\Module\Model;

use Magento\Framework\Event\ManagerInterface as EventManager;

class PointsManager
{
    private $eventManager;

    public function __construct(EventManager $eventManager)
    {
        $this->eventManager = $eventManager;
    }

    public function awardPoints($customerId, $points)
    {
        // Award points logic...

        // Dispatch custom event so other modules can react
        $this->eventManager->dispatch(
            'vendor_module_points_awarded',
            [
                'customer_id' => $customerId,
                'points' => $points,
                'customer' => $this->customerRepository->getById($customerId)
            ]
        );
    }
}

Event Naming Conventions

Follow Magento's naming conventions for custom events:

{module}_{action}_{context}
Pattern Example
vendor_module_points_awarded Points awarded in loyalty module
vendor_module_order_export_before Before order export
vendor_module_api_request_send_after After API request
vendor_module_customer_sync_after After customer sync

Use lowercase with underscores. The name should clearly describe when the event fires.

Listening to Custom Events

Other modules listen to your custom event using the same events.xml structure:

<event name="vendor_module_points_awarded">
    <observer name="other_module_points_listener"
              instance="Other\Module\Observer\PointsAwarded"/>
</event>
<?php
namespace Other\Module\Observer;

use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Event\Observer;

class PointsAwarded implements ObserverInterface
{
    public function execute(Observer $observer)
    {
        $customerId = $observer->getEvent()->getData('customer_id');
        $points = $observer->getEvent()->getData('points');

        // Send email notification, update external loyalty system, etc.
    }
}

Global vs Area Events

Events declared in different area files fire only in those areas:

  • etc/frontend/events.xml — observers only run during storefront requests
  • etc/adminhtml/events.xml — observers only run during admin requests
  • etc/events.xml (global) — observers run in all areas

This is important for performance. If an observer only makes sense on the frontend (like sending a post-checkout email), declare it in etc/frontend/events.xml. The observer is not loaded during admin requests, saving memory and execution time.

Performance Considerations

Observers run synchronously during the request. A slow observer delays the page response. Consider these strategies:

  • Keep observers fast. Database queries, API calls, and file operations in observers should be minimal. If you need to perform heavy work (sending emails, syncing with external systems), use Magento's message queue system instead.

  • Use shared="false" judiciously. Setting shared="false" creates a new observer instance for every event dispatch. This increases memory usage. Only disable sharing if the observer maintains state that changes between dispatches.

  • Avoid long-running operations in checkout events. Events like checkout_submit_all_after execute during the checkout process. If your observer takes 10 seconds to call an external API, the customer waits 10 seconds for the order confirmation page. Use async processing for heavy tasks.

  • Register events in the correct area. An observer declared in etc/adminhtml/events.xml is never loaded on the frontend, reducing memory and improving performance.

Using Message Queues for Heavy Observers

For heavy operations, dispatch a message to a queue instead of executing inline:

public function execute(Observer $observer)
{
    $order = $observer->getEvent()->getOrder();

    // Instead of API call here, publish to queue
    $this->messageQueue->publish('order.sync', $order->getId());
}

The queue consumer processes the message asynchronously, keeping the checkout fast.

Events vs Plugins

When should you use an event observer vs a plugin?

Scenario Best Approach
React to an action (order placed, product saved) Event observer
Modify method arguments or return values Plugin
Fire-and-forget notifications Event observer
Many extensions need to react to same action Event observer
Need to conditionally skip execution Plugin (around)
No event exists for your use case Plugin (more granular control)
Integrate with external systems Event observer (for decoupling)

In general: use events when you want to react to something happening, use plugins when you want to modify how something works.

Common Mistakes

  1. Using events when a plugin is more appropriate. Events only tell you that something happened. They do not let you modify the data flowing through the system. If you need to change the product name before it is saved, a before plugin on save is more appropriate than an observer on catalog_product_save_before.

  2. Wrong area declaration. Placing an observer in etc/frontend/events.xml when it should be in etc/adminhtml/events.xml causes the observer to never fire for admin actions. Always verify the area where your event fires.

  3. Forgetting to clear caches. Event observer declarations are cached. If you add a new observer and it does not fire, run bin/magento cache:clean config or bin/magento cache:flush to refresh the event configuration cache.

  4. Heavy synchronous operations. Making HTTP requests or running complex database queries inside an observer blocks the page response. Customers experience slow checkout, slow product saves, or timeouts. Use message queues for any operation that takes more than a few milliseconds.

  5. Not checking if data exists. Event data is not guaranteed to be present. Always check $observer->getEvent()->getOrder() for null before using it. Some events fire in multiple contexts, and certain data might not be available in all contexts.

Practice Questions

  1. What is the difference between a global event and an area-specific event? Answer: A global event (declared in etc/events.xml) fires in all areas: frontend, adminhtml, web API, and GraphQL. An area-specific event (declared in etc/frontend/events.xml or etc/adminhtml/events.xml) only fires in that area. Area-specific declarations improve performance by only loading observers when needed.

  2. How do you access the order object in a sales_order_place_after observer? Answer: Use $observer->getEvent()->getOrder(). The sales_order_place_after event passes the order object as event data. If the order might not be available, check for null before accessing its methods.

  3. What are the advantages of dispatching custom events in your module? Answer: Custom events allow other modules to react to your module's actions without modifying your code. This enables extension developers to integrate with your module without conflicts. It also decouples your module's core logic from secondary concerns like email notifications, API syncs, and logging.

  4. Challenge: Create a module that introduces a "Gold Customer" tier. Use an observer on checkout_submit_all_after to calculate the customer's total spend after each order. If the total exceeds $1000, dispatch a custom event vendor_loyalty_gold_status_earned. Create a second observer that listens to this custom event and sends a congratulatory email. Use area-specific declarations appropriately.

FAQ

What is the ObserverInterface in Magento?

ObserverInterface is the contract that all observer classes must implement. It defines a single method execute(Observer $observer) that Magento calls when the event is dispatched. The Observer object provides access to the event data via getEvent() and the specific data objects via getter methods like getOrder(), getProduct(), and getCustomer().

How do I find which events are available in Magento?

There are several ways: search the Magento codebase for $this->_eventManager->dispatch( to see event dispatches; use the n98-magerun2 tool with dev:event:list command; or inspect core events.xml files in vendor/magento/. The Magento developer documentation also maintains an event reference list.

Can I stop an event from propagating to other observers?

No, Magento does not support stopping event propagation. All registered observers for an event always execute. If you need to prevent certain behavior, use a plugin on the dispatching method instead of an event observer. Alternatively, check conditions inside your observer and skip execution when appropriate.

{{< faq "What is the shared attribute in the observer declaration?" "The shared attribute controls whether the observer instance is reused across multiple dispatches of the same event (shared=\"true\", default) or a new instance is created each time (shared=\"false\"). Use shared=\"false\" when the observer maintains state that should not persist between dispatches." >}}

Do observers work in GraphQL and REST API requests?

Yes. Events dispatched during API requests trigger observers registered in etc/webapi_rest/events.xml or etc/events.xml (global). If your observer should only run during REST API requests, register it in etc/webapi_rest/events.xml. The same applies to GraphQL with etc/graphql/events.xml.

Mini Project

Your task: Create an order tracking and notification module using events.

  1. Create Vendor_OrderTracker module with registration.php and module.xml.
  2. Create an observer on sales_order_place_after that saves the order data to a custom vendor_ordertracker_log table with columns: log_id, order_id, customer_email, total, status, created_at.
  3. Create an observer on sales_order_shipment_save_after that sends an email notification to the customer when their order ships. Use the TransportBuilder class to send the email.
  4. Create a custom event vendor_ordertracker_order_export dispatched from a CLI command. Another observer listens to this event and exports the order to a CSV file in var/export/.
  5. Create the Setup/InstallSchema.php for the custom table.
  6. Enable the module and place a test order. Verify the log entry is created and the shipment triggers an email.
  7. Run the CLI command and verify the CSV export contains the order data.

This exercise covers the most common event-driven patterns in Magento: data logging, email notifications, and data export. These are the foundations of most B2B and integration projects.

What's Next

Now that you understand all four extension mechanisms (preferences, plugins, events, and observers), the next module covers API development:

Continue to Lesson 29: REST API and GraphQL — Build and consume Magento APIs.

Related lessons:

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro