What a Module Is

A module is a Vendor/Module folder under app/code, made real by registration.php and etc/module.xml.

Syntaxapp/code/<Vendor>/<ModuleName>/registration.php + app/code/<Vendor>/<ModuleName>/etc/module.xml

Magento is not a framework you bolt features onto from outside. Everything is a module: a directory of PHP and XML that adds one feature and can be switched off. The checkout is a module. The admin notifications are a module. Your code is a module too.

A module has a two-part name, a vendor and a module name joined by an underscore, as in Acme_Faq. The directory mirrors the name.

app/code/Acme/Faq/
├── registration.php
├── etc/
│   └── module.xml
└── composer.json   (optional while the module lives in app/code)

The two files that make it exist

registration.php tells Magento the module's name and its absolute path. etc/module.xml declares the module to the configuration layer and can name modules that must load before it. The two halves fail differently when one is missing. A directory without registration.php is never registered, so it is simply absent and nothing is reported. A module that registers itself but ships no etc/module.xml is worse: Magento\Framework\Module\ModuleList\Loader walks every registered module path and reads <path>/etc/module.xml unconditionally, so the missing file throws a FileSystemException on every request.

Why exactly two levels deep

At boot Magento reads the glob patterns in app/etc/registration_globlist.php. For modules the pattern is app/code/*/*/registration.php: one vendor folder, one module folder. A file at app/code/Faq/registration.php, or one nested a level deeper, is never found.

What trips people up

Creating the files does not switch the module on. Run bin/magento setup:upgrade, which scans app/code, records anything new in app/etc/config.php as enabled and applies its schema. Then run bin/magento cache:flush, because the merged configuration is cached. In production mode also run bin/magento setup:di:compile.

Example

Example · php
<?php
// app/code/Acme/Faq/registration.php

declare(strict_types=1);

use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Acme_Faq', __DIR__);

When to use it

  • Starting a new feature and getting Magento to see a fresh app/code/Acme/Faq directory.
  • Working out why a module copied onto a server does not appear in bin/magento module:status.
  • Deciding whether new code belongs in app/code or in a Composer package under vendor.
  • Reading an unfamiliar third-party extension by starting from its registration.php and module.xml.

More examples

etc/module.xml, the second required file

The smallest valid module.xml. The name must match the name passed to ComponentRegistrar exactly. This file is what makes Magento treat the directory as a configuration source, so its etc/ files are merged into the application config. After adding it, run bin/magento setup:upgrade and bin/magento cache:flush.

Example · xml
<!-- 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"/>
</config>

The same mechanism registers a theme

Modules are one kind of component. Themes, language packages and libraries register through the same static call with a different type constant and a different name format. Note the path: themes are globbed as app/design/*/*/*/registration.php, three levels, because the area name is part of the path. Reach for this when you need to tell whether an unfamiliar directory is a module or a theme: read its registration.php.

Example · php
<?php
// app/design/frontend/Acme/aurora/registration.php

declare(strict_types=1);

use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(ComponentRegistrar::THEME, 'frontend/Acme/aurora', __DIR__);

A module installed under vendor/ instead

Under vendor/, the glob never runs. Composer's generated autoloader includes every path in autoload.files, which is how registration.php executes, and psr-4 resolves the classes. The module is identical in every other way, which is why a third-party extension and your own app/code module look the same from inside. Use this form once the module has to be installed into more than one project.

Example · json
{
    "name": "acme/module-faq",
    "type": "magento2-module",
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Acme\\Faq\\": ""
        }
    }
}

Discussion

  • Be the first to comment on this lesson.