What is a Magento Observer?
Short answer A Magento 2 observer is a class that runs when Magento dispatches an event, such as an order being placed or a customer being saved. That is different from a plugin, which wraps a method call. You register observers in etc/events.xml (all areas), etc/frontend/events.xml or etc/adminhtml/events.xml, and the class implements Magento\Framework\Event\ObserverInterface with a single execute(Observer $observer) method. Observers run synchronously, inside the same request. So when an observer has to call an ERP or another slow API, I publish a message to a queue and let a consumer do the work.
- Identify the event you care about 01
- Create the observer class 02
- Register the observer in events.xml 03
- Clear the config cache 04
- Event fires at runtime: execute() runs synchronously 05
Most common mistake Doing slow work in a synchronous observer
Five steps from event name to a live observer in production
Observers are wired up in XML, not found automatically. You find the event, write the class, register it in events.xml and clear the config cache. This is the sequence I follow.
-
01
Identify the event you care about
Core code dispatches a lot of named events. The ones I use most are
customer_save_after,customer_login,sales_order_place_after,catalog_product_save_after,cms_page_save_after,checkout_cart_product_add_after,controller_action_predispatchandadmin_user_authenticate_after. Adobe has an event reference, but I trust a grep more: searchvendor/magentofor->dispatch('and you see every event your installed version actually fires, along with the data it passes. -
02
Create the observer class
The class goes in
Vendor\Module\Observer\WhateverNameand implements\Magento\Framework\Event\ObserverInterface, which has one method:execute(\Magento\Framework\Event\Observer $observer): void. Read the event data with$observer->getEvent()->getData('customer'), or the magic getter$observer->getCustomer(), which reads the same key. Anything else you need (a logger, a repository, a queue publisher) comes in through the constructor like any other Magento class. -
03
Register the observer in events.xml
Choose the scope on purpose.
etc/events.xmlapplies everywhere, including cron, CLI and the APIs.etc/frontend/events.xmlapplies only to storefront requests andetc/adminhtml/events.xmlonly to the admin. The entry looks like<event name="customer_save_after"><observer name="my_unique_name" instance="Vendor\Module\Observer\CustomerSaveAfter"/></event>. Keep the observernameunique for that event. If another module uses the same name, the later one replaces the earlier one without any error. -
04
Clear the config cache
Magento merges every
events.xmlinto the config cache, so a new observer does nothing until that cache is cleared. Runbin/magento cache:clean configafter anyevents.xmlchange, in every mode, including developer mode if the config cache is enabled. In production mode the class also needssetup:di:compile. When someone tells me their observer never fires, a stale config cache is the first thing I check. -
05
Event fires at runtime: execute() runs synchronously
When core code calls
$eventManager->dispatch('customer_save_after', ['customer' => $customer]), Magento’s event manager looks up the event name, walks the registered observer list inevents.xmlload order (no explicitsortOrder), and invokes eachexecute()synchronously. An exception in one observer stops the chain unless caught, so be defensive: wrap risky work intry/catchand log rather than re-throw if the failure is non-critical to the dispatching request.
Four scenarios where an observer is the right tool
Observers are not the answer to every change. Plugins, preferences and di.xml arguments all have their place. These are the four cases where I pick an observer.
-
Side-effect-on-state-change patterns
“When a customer registers, post to Slack.” “When an order is placed, send the lines to the ERP.” “When a product is saved, purge that URL on the CDN.” Anything shaped like “this happened, now do that” is what observers are for. If the follow-up talks to another system over HTTP, I keep the observer tiny: it publishes a message to a queue (declared in
communication.xmlandqueue_publisher.xml) and a consumer does the slow part outside the customer’s request. -
Loose-coupling between modules
An observer lets your module react to core behaviour without calling or extending core classes. Magento fires the event, your code runs, and the core class has no idea your module exists. Event names change less often between versions than the internals of the methods a plugin wraps. So when an event and a plugin would both do the job, I usually go with the event.
-
Replacing legacy Magento 1 hooks
Magento 1 used
Mage::dispatchEvent('name', $params)with observers declared inconfig.xml. Magento 2 events are the direct equivalent, same dispatch-and-react pattern, slightly different wiring (events.xmlinstead ofconfig.xml,ObserverInterfaceinstead ofMage_Core_Model_Observer). Most Magento 1 observer logic ports cleanly to Magento 2 observers; if you’re mid-migration, observers are the migration path of least resistance for event-reactive code. -
Subscribing to your OWN dispatched events
Your module dispatches
vendor_module_action_afterfrom inside a service class, and a different module observes it, clean separation of concerns. This is also how you make your module extensible by third parties without forcing them to use plugins on your code. Dispatch your own events at meaningful state transitions in your module (panth_subscription_renewed_after,panth_widget_rendered_after) and downstream modules can hook in without touching your code.
Three observer mistakes that slow your store or silently break it
Every Magento observer-related ticket I get called in on is one of these three. Check your code against this list before shipping.
-
Doing slow work in a synchronous observer
Observers fire synchronously inside the dispatching request, an HTTP call to a CRM from inside
customer_save_afterblocks the admin customer-save by the duration of the CRM round-trip. A 2-second CRM call turns a 200ms admin save into a 2.2s admin save. Always queue async viaMagento_AsynchronousOperations(orPublisherInterface) for any I/O over~50ms. The synchronous observer publishes a tiny message; a consumer process handles the slow work out-of-band. -
Forgetting events.xml is per-area
Registering an observer in
etc/events.xmlwhen you only need it on the frontend doubles its boot footprint, the observer wires up in adminhtml too, gets invoked on every matching admin event, and adds DI overhead to every admin page load. Pick the scoped variant:etc/frontend/events.xmlfor storefront-only,etc/adminhtml/events.xmlfor admin-only, plainetc/events.xmlonly when you genuinely need both. Same applies to webapi_rest / webapi_soap scopes for REST/SOAP-only observers. -
Mutating $observer data and expecting core to see it
Observers receive a copy of the event payload wrapped in the
Observerobject, modifying values inside the observer doesnotpropagate back to the caller. If you change$observer->getOrder()->setStatus('custom')the change may or may not persist depending on whether the dispatched object is the same reference Magento later saves. For deterministic mutation use aplugin(around / before / after) on the specific method, not an observer, observers are for reactions, plugins are for interception.
Magento observers, frequently asked questions
-
Observer vs plugin, when to use which?
Use an observer when you want to react to a dispatched event with a side effect that does not need to influence the caller’s return value, logging, queue publishing, sending an email, invalidating a cache. Use a plugin when you want to wrap a specific method call to inspect or modify its arguments, return value, or exception handling, that’s the interceptor pattern, and observers cannot do it. Rule of thumb: if you need to change what the caller sees, you need a plugin; if you just need to do something on the side, an observer is cleaner and survives upgrades better. They are not mutually exclusive, many modules use both for different concerns. -
Why isn’t my observer firing?
Three causes account for almost every case. First, theevents.xmlisn’t loaded, runbin/magento cache:flush config full_pageafter everyevents.xmlchange. Second, the observer is in the wrong area scope, an observer inetc/adminhtml/events.xmlwill not fire on frontend requests, and vice versa. Check whether your event is dispatched in frontend code, admin code, or both, and place theevents.xmlaccordingly. Third, the event name is typo’d, Magento does not throw an error for an observer registered against a non-existent event, it just silently never invokes. Grepvendor/magentofor the exact event string to confirm it’s dispatched. -
Can multiple observers listen to one event?
Yes, unlimited, each registered with a uniquenameattribute on the<observer>element. They fire inevents.xmlload order across modules (which roughly follows module dependency order), with no explicitsortOrderattribute available. If you absolutely need ordering, the workaround is to make one observer depend on the other’s output via a shared registry or a database flag, but that’s a code-smell. If you find yourself needing strict ordering between observers, reconsider whether a plugin (which does supportsortOrder) is the better fit. -
How do I make an observer async?
Two patterns. The lightweight option: from inside the synchronous observer, publish a message viaMagento\Framework\MessageQueue\PublisherInterface::publish('topic.name', $payload), declare the topic + consumer inetc/communication.xml+etc/queue_consumer.xml+etc/queue_topology.xml, and run a consumer process to handle it. The richer option: use theMagento_AsynchronousOperationsmodule which gives you bulk operations, status tracking via the admin UI, and per-operation retry semantics. Either way the synchronous observer stays sub-50ms (it just publishes the message) and the slow work runs out-of-band on a consumer process. -
What’s the cost of a no-op observer?
Roughly5-15 microsecondsper dispatch, class autoload (cached after the first call), DI container resolution, and the emptyexecute()method call. The cost adds up because Magento ships hundreds of dispatched events per request:controller_action_predispatchfires for every controller,core_layout_block_create_afterfires for every block,view_block_abstract_to_html_beforefires for every render. If you register an observer on a high-frequency event, the cumulative cost can become measurable. Profile with Blackfire or XHProf before registering observers on the high-fire events. -
Are there any global events for “any database write”?
No, Magento dispatches per-entity-type events, not a single “model_save_after” superset. You getcustomer_save_after,catalog_product_save_after,sales_order_place_after,cms_page_save_after, and so on per entity. There used to be a genericmodel_save_afterin Magento 1, but Magento 2 removed it because almost every observer registered against it ended up needing to check the model type anyway, which made the abstraction worse than just listening to the specific entity event. If you genuinely need to react to every save across types, register the same observer class on each event you care about, that’s the idiomatic Magento 2 approach.
Need a Magento observer built or audited?
Send your event + side-effect requirement, I will design the observer, place it in the correct area scope, wire async via Magento_AsynchronousOperations where the work is slow, and ship a tested module with a fixed-price quote. 24-business-hour written reply.