Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Cleanly Managing Module Dependencies (the module.xml sequence)

Cleanly Managing Module Dependencies (the module.xml sequence)

~6 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026

The <sequence> in module.xml has grown five times across this series - always for the same reason: a foreign key or an EAV dependency needed the guarantee that another table already existed before db_schema.xml or a data patch touched it. This chapter pulls the five individual decisions from chapters 2, 11, 19/20, and 22 together into one final module.xml - and explains, just as precisely, why four seemingly obvious candidates deliberately aren't on the list.

The final sequence

app/code/Mironsoft/Loyalty/etc/module.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Mironsoft_Loyalty">
        <sequence>
            <module name="Magento_Customer"/>
            <module name="Magento_Sales"/>
            <module name="Magento_Eav"/>
            <module name="Magento_Catalog"/>
            <module name="Magento_Company"/>
        </sequence>
    </module>
</config>

Why each individual entry

  • Magento_Customer (chapter 2): the foreign key customer_id on the points ledger (chapter 3) references customer_entity.entity_id, and it's also the target of the customer attributes loyalty_points_balance/loyalty_tier (chapter 21).
  • Magento_Sales (chapter 2): the foreign key order_id on the ledger references sales_order.entity_id, and it's the target of the sales attributes loyalty_points_earned/loyalty_points_redeemed (chapters 23/63).
  • Magento_Eav (chapter 11): the reward entity (chapters 10-18) depends on the entire EAV table set (eav_attribute, eav_attribute_set, eav_entity_type), which only Magento_Eav itself creates.
  • Magento_Catalog (chapters 19/20): product and category attributes, plus - from block 9 onward - the custom product type register against catalog_product/catalog_category and against core base classes like Type\Virtual.
  • Magento_Company (chapter 22): the company attribute loyalty_tier_override attaches to CompanyInterface via extension_attributes.xml - without this module present, XML validation itself fails, not merely setup:upgrade.

What's deliberately missing - and why that's not an oversight

Four more modules show up in the code without ever getting their own sequence entry: Magento_Quote (the new loyalty_points_to_redeem column, chapter 63), Magento_Checkout/Magento_Payment (the payment method, chapters 62-65), Magento_Shipping (the shipping method, chapters 66-69), and Magento_Widget/Magento_PageBuilder (chapters 55-61).

  • Widget and Page Builder integrate purely through their own, purely declarative configuration files (widget.xml, etc/pagebuilder/content_type.xml) - these mechanisms read at runtime, not during setup, so there's no race against a table that doesn't exist yet.
  • Payment, checkout, and shipping are registered the classic way, through etc/config.xml (payment/*/model, carriers/*/model) and etc/di.xml - also resolved at runtime, no schema access during setup.
  • Quote is the one genuinely debatable case: loyalty_points_to_redeem (chapter 63) is appended to the foreign quote table via db_schema.xml - the same kind of dependency as the ledger. Chapter 63 explicitly justifies skipping its own entry by noting that Magento_Quote itself internally depends on Magento_Customer, Magento_Sales, and Magento_Eav - all three already sit in our own sequence. Since Magento's module sorter combines the sequence declarations of every installed module into one global order, Magento_Quote ends up transitively ahead of Mironsoft_Loyalty anyway.

Achtung: This transitive reasoning is a deliberate, documented trade-off of this series - not a general rule for your own practice. It only works as long as Magento's core modules don't change their own internal dependencies, which is outside your control. For a real production module, the more robust choice is almost always an explicit, direct sequence entry for every table you actually touch - even when it supposedly already follows transitively.

What the sequence actually controls - and what it doesn't

A common misconception: sequence does not control whether a plugin, observer, or preference from a foreign module takes effect at runtime - that's governed entirely by di.xml/events.xml, independent of module order. sequence only affects setup: the order in which setup:upgrade applies db_schema.xml changes and runs data patches. A plugin on a foreign class therefore, unlike a foreign-key column, fundamentally doesn't need its own sequence entry just because the target module has to exist - a Composer require already covers that.

Tipp: A rule of thumb for your own practice: add a sequence entry exactly when your own db_schema.xml or a data patch (EavSetup, SalesSetup, a foreign table) touches schema elements of another module. For purely runtime extensions (plugin, observer, preference, widget, payment method), the Composer dependency is enough.

With a correct sequence in place, the last open question of the base architecture is settled - chapter 99 turns next to the order in which a module this large actually gets rolled out to production.