registration.php and module.xml
registration.php names the module and its path; etc/module.xml declares it and sets load order.
ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Vendor_Module', __DIR__);These two files are the whole contract between your directory and Magento. One runs at boot, the other is read as configuration.
registration.php
ComponentRegistrar::register() is static and takes three arguments: the component type, the fully qualified component name and an absolute path. Pass __DIR__ for the path so the file keeps working wherever the module is installed. The type constants are MODULE, THEME, LANGUAGE, LIBRARY and SETUP.
Shorthand and longhand are the same call. Core modules import the class, but the fully qualified form is identical:
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Acme_Faq',
__DIR__
);etc/module.xml
The name attribute must match the registered name. The schema, urn:magento:framework:Module/etc/module.xsd, requires both halves to begin with an uppercase letter, so acme_faq is rejected.
<sequence> lists modules that must load first. It affects order only: configuration merge order, observer order, layout processing order. It installs nothing. The real dependency belongs in composer.json under require.
setup_version
The schema still accepts an optional setup_version attribute and older tutorials set it, but nothing triggers off it any more. Since 2.3, schema changes come from db_schema.xml and data changes from data patches, and both track themselves. Core modules in 2.4.9 ship module.xml without it.
After editing either file
Run bin/magento setup:upgrade, then bin/magento cache:flush.
Example
<?php
// app/code/Acme/Faq/registration.php
declare(strict_types=1);
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Acme_Faq', __DIR__);
// The same call written without the import. Identical behaviour:
//
// \Magento\Framework\Component\ComponentRegistrar::register(
// \Magento\Framework\Component\ComponentRegistrar::MODULE,
// 'Acme_Faq',
// __DIR__
// );
When to use it
- Creating the first two files of a new module and getting the names to match.
- Fixing a module that is ignored because module.xml and registration.php disagree on the name.
- Making your module's configuration merge after another module's by adding a sequence entry.
- Modernising an inherited module that still relies on setup_version and upgrade scripts.
More examples
Declaring load order with sequence
Acme_Faq now loads after Magento_Store and Magento_Catalog, so its configuration merges on top of theirs. Reach for sequence when your module overrides or extends configuration another module provides and the result depends on which is read last. It does not guarantee the other module is installed; add the matching entry to composer.json require for that. Apply with bin/magento setup:upgrade followed by bin/magento cache:flush.
<!-- app/code/Acme/Faq/etc/module.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Acme_Faq">
<sequence>
<module name="Magento_Store"/>
<module name="Magento_Catalog"/>
</sequence>
</module>
</config>
How registration.php runs for a Composer-installed module
Nothing calls registration.php by name. In app/code it is picked up by a glob; in vendor/ it is executed because it is listed in autoload.files, which Composer compiles into the generated autoloader. Forgetting that line is the usual reason a module installed by Composer behaves as if it is not there at all.
{
"name": "acme/module-faq",
"description": "Customer-facing FAQ pages",
"type": "magento2-module",
"license": "proprietary",
"require": {
"php": "~8.4.0",
"magento/framework": "103.0.*"
},
"autoload": {
"files": [
"registration.php"
],
"psr-4": {
"Acme\\Faq\\": ""
}
}
}
The same function for a language package
Language packages use the LANGUAGE type and a lowercase vendor_locale name, and are globbed from app/i18n/*/*/registration.php. Useful when you need to ship translations separately from the module that produces the strings, so the same module can serve several locales without a rebuild.
<?php
// app/i18n/acme/fr_fr/registration.php
declare(strict_types=1);
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(ComponentRegistrar::LANGUAGE, 'acme_fr_fr', __DIR__);
Discussion