Controlling Observer Order and Priority
Controlling Observer Order and Priority
~6 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026
Chapter 35 showed that a single event can have several independent observers - mironsoft_loyalty_customer_tier_changed could interest a marketing module AND an analytics module at the same time. What if their relative order actually matters? The short answer: unlike plugins, events.xml has no explicit sortOrder attribute. This chapter explains what the order depends on instead.
Order within one file
If the same events.xml file registers several <observer> nodes for the same <event>, they run in exactly the order they appear in the document - top to bottom, predictable.
Order across modules
If two different modules each register an observer on the same event, module load order determines execution order - the same topological sort driven by <sequence> entries in module.xml that also governs di.xml and layout merges. A module that needs to load after Mironsoft_Loyalty simply adds it to its own sequence:
<!-- app/code/Vendor/Analytics/etc/module.xml, illustrative -->
<module name="Vendor_Analytics">
<sequence>
<module name="Mironsoft_Loyalty"/>
</sequence>
</module>This guarantees that Vendor_Analytics's observer on mironsoft_loyalty_customer_tier_changed always runs after any observer Mironsoft_Loyalty itself might register on the same event - without touching a single line of events.xml.
The name attribute as a merge key
Two <observer> nodes with the same name - even from different modules - merge into a single one, exactly like block and layout XML. A later-loading module can therefore disable a third-party observer via disable="true" or override its instance, without touching that module's source code.
<!-- Disabling another module's observer, illustrative -->
<event name="mironsoft_loyalty_customer_tier_changed">
<observer name="vendor_marketing_notify_tier_upgrade" disable="true"/>
</event>The shared attribute
By default (shared="true", implicit), Magento reuses a single observer instance across every dispatch - harmless for AwardPointsOnOrderPlaced and ReversePointsOnCreditmemoSave, since both work purely through constructor injection and hold no mutable state between calls - the same statelessness PointsCalculator (chapter 5) has had from the start. shared="false" forces a fresh instance per dispatch and would only matter if an observer actually held its own mutable state between execute() calls - that never happens anywhere in this module.
When order genuinely matters
- Adjust module
sequence(see above) - but this affects every shared event between the two modules, not just the one that currently matters. - Merge both concerns into a single observer instead of relying on ordering between two separate ones - eliminates the problem, at the cost of decoupling.
- Move truly order-critical logic to a plugin, which does have an explicit, numeric
sortOrder(chapter 37 leads directly there).
Tipp: These three options form a ranking, not an equal menu: try option 1 first, reach for option 2 only with genuine substantive cohesion between both concerns, and treat option 3 as a last resort for when order is truly a core requirement - not just a convenient assumption.
That nearly wraps up block 4's content - chapter 37 explicitly summarizes the "observer or plugin?" decision that's been implicit throughout, before block 5 starts covering plugins in detail.