Skip to content

Magento Module Structure — module.xml, registration.php and Architecture

DodaTech Updated 2026-06-27 12 min read

In this tutorial, you'll learn how Magento modules are structured, how to create a new module with registration.php and module.xml, and how to enable it in your Magento installation.

What You'll Learn

  • The complete directory structure of a Magento module
  • How to create registration.php and module.xml
  • The purpose of each directory: Block, Controller, Model, etc, Setup, view
  • How to enable a module with bin/magento commands
  • How to configure module dependencies and setup versions

Why It Matters

Everything in Magento is a module. The catalog, sales, checkout, customer management — all are modules. When you write custom functionality, you create a module. When you install a third-party extension, you add a module. Understanding the module structure is the foundation of all Magento development. Without this knowledge, you cannot create custom features, integrate third-party systems, or even debug why an extension is not working.

Real-World Use

A retail chain needs a custom loyalty points system. Customers earn points on every purchase and redeem them at checkout. The system needs custom database tables (Setup), admin configuration (etc/adminhtml), API endpoints (Controller/Api), frontend display (view/frontend), and email notifications (etc/email_templates). All of this lives inside a single module directory at app/code/Vendor/Loyalty/. The module structure organizes each concern into its own subdirectory, keeping the code maintainable as the system grows to handle millions of loyalty transactions.

Learning Path

flowchart LR
  A["25: Module Structure
You are here"]:::current A --> B["26: Dependency Injection"] B --> C["27: Plugins"] C --> D["28: Observers and Events"] classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px

What Is a Magento Module

A Magento module is a directory containing PHP classes, configuration XML files, and templates that add or modify functionality. Modules are the building blocks of Magento. Every feature you see in the admin panel and storefront comes from a module.

Modules live in app/code/VendorName/ModuleName/. The vendor name is usually your company or personal namespace. The module name identifies the functionality.

A module minimally requires two files to be recognized by Magento:

  • registration.php — registers the module with Magento's component registry
  • etc/module.xml — declares the module's name, version, and dependencies

Directory Structure

Here is the complete directory structure of a typical Magento module:

app/code/Vendor/Module/
├── registration.php
├── etc/
│   ├── module.xml
│   ├── config.xml
│   ├── di.xml
│   ├── routes.xml
│   ├── events.xml
│   ├── adminhtml/
│   │   ├── di.xml
│   │   ├── routes.xml
│   │   └── events.xml
│   └── frontend/
│       ├── di.xml
│       ├── routes.xml
│       └── events.xml
├── Block/
│   ├── Index.php
│   └── Adminhtml/
│       └── Grid.php
├── Controller/
│   ├── Index/
│   │   └── Index.php
│   └── Adminhtml/
│       └── Index/
│           └── Index.php
├── Model/
│   ├── ResourceModel/
│   │   ├── Example.php
│   │   └── Collection.php
│   └── Example.php
├── Setup/
│   ├── InstallSchema.php
│   ├── InstallData.php
│   └── UpgradeSchema.php
├── view/
│   ├── frontend/
│   │   ├── layout/
│   │   │   └── default.xml
│   │   ├── templates/
│   │   │   └── example.phtml
│   │   └── web/
│   │       ├── js/
│   │       │   └── example.js
│   │       └── css/
│   │           └── example.css
│   └── adminhtml/
│       ├── layout/
│       ├── templates/
│       └── web/
├── i18n/
│   └── en_US.csv
├── Api/
│   ├── Data/
│   │   └── ExampleInterface.php
│   └── ExampleRepositoryInterface.php
└── Test/
    ├── Unit/
    └── Integration/

registration.php

The registration.php file tells Magento's autoloader that a module exists and where to find its classes. Every module must have one.

<?php

use Magento\Framework\Component\ComponentRegistrar;

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

The first argument specifies the component type. For modules, it is always ComponentRegistrar::MODULE. Other options include THEME, LANGUAGE, and LIBRARY.

The second argument is the module name in Vendor_Module format. This must exactly match the module's directory name and the name declared in module.xml.

The third argument is the directory path. __DIR__ refers to the module's root directory. Magento uses this path to resolve class names and locate resource files.

etc/module.xml

The module.xml file declares the module's identity to Magento's module system. It specifies the module name, setup version, and dependencies on other modules.

<?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="Vendor_Module" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Catalog"/>
            <module name="Magento_Sales"/>
        </sequence>
    </module>
</config>

The name attribute matches the module name from registration.php. The setup_version tracks schema and data updates. Magento uses this version to determine which setup scripts to run.

The <sequence> element declares dependencies. Magento loads modules in the correct order based on these declarations. If your module depends on Magento_Catalog, Magento ensures the catalog module is loaded and its database schemas are set up before your module.

etc/config.xml

The config.xml file stores default configuration values. These values populate the admin configuration when the module is first installed.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <vendor_module>
            <general>
                <enabled>1</enabled>
                <api_endpoint>https://api.example.com</api_endpoint>
                <timeout>30</timeout>
            </general>
        </vendor_module>
    </default>
</config>

Configuration values are accessed in PHP via:

$this->scopeConfig->getValue('vendor_module/general/enabled', \Magento\Store\Model\ScopeInterface::SCOPE_STORE);

etc/di.xml

The di.xml file configures Magento's Dependency Injection container. This is where you define class preferences, plugin configurations, and constructor argument injection. We cover this in the next lesson.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Vendor\Module\Api\ExampleRepositoryInterface"
                type="Vendor\Module\Model\ExampleRepository"/>
</config>

etc/routes.xml

The routes.xml file registers frontend or admin routes for your module.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="standard">
        <route id="vendor_module" frontName="vendor_module">
            <module name="Vendor_Module"/>
        </route>
    </router>
</config>

The frontName is the URL path segment. If your frontName is vendor_module, URLs look like http://example.com/vendor_module/controller/action/.

For admin routes, place the file in etc/adminhtml/routes.xml with router id admin.

etc/events.xml

Events allow your module to react to actions in other modules. The events.xml file maps event names to Observer classes.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="checkout_cart_add_product_complete">
        <observer name="vendor_module_add_to_cart" instance="Vendor\Module\Observer\AddToCart" />
    </event>
</config>

Block Directory

The Block/ directory contains block classes that render PHTML templates. Each block class extends Magento\Framework\View\Element\Template and provides data to its template.

<?php
namespace Vendor\Module\Block;

use Magento\Framework\View\Element\Template;

class Greeting extends Template
{
    protected $_template = 'Vendor_Module::greeting.phtml';

    public function getGreeting(): string
    {
        return 'Welcome to our custom module!';
    }
}

The $_template property defines the default template path. You can also pass a template via layout XML using the template attribute on the <block> element.

Controller Directory

The Controller/ directory contains action classes that handle HTTP requests. Each action class executes a specific task and returns a response.

<?php
namespace Vendor\Module\Controller\Index;

use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;

class Index extends Action
{
    protected $resultPageFactory;

    public function __construct(Context $context, PageFactory $resultPageFactory)
    {
        parent::__construct($context);
        $this->resultPageFactory = $resultPageFactory;
    }

    public function execute()
    {
        return $this->resultPageFactory->create();
    }
}

The URL for this controller would be http://example.com/vendor_module/index/index (route vendor_module, controller index, action index).

Model Directory

The Model/ directory contains business logic classes: models, resource models, and collections.

Model class:

<?php
namespace Vendor\Module\Model;

use Magento\Framework\Model\AbstractModel;

class Example extends AbstractModel
{
    protected function _construct()
    {
        $this->_init(\Vendor\Module\Model\ResourceModel\Example::class);
    }
}

ResourceModel class:

<?php
namespace Vendor\Module\Model\ResourceModel;

use Magento\Framework\Model\ResourceModel\Db\AbstractDb;

class Example extends AbstractDb
{
    protected function _construct()
    {
        $this->_init('vendor_module_table', 'entity_id');
    }
}

Collection class:

<?php
namespace Vendor\Module\Model\ResourceModel\Example;

use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;

class Collection extends AbstractCollection
{
    protected function _construct()
    {
        $this->_init(
            \Vendor\Module\Model\Example::class,
            \Vendor\Module\Model\ResourceModel\Example::class
        );
    }
}

Setup Directory

The Setup/ directory contains installation and upgrade scripts that create database tables and populate default data.

InstallSchema

Creates database tables when the module is first installed:

<?php
namespace Vendor\Module\Setup;

use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;

class InstallSchema implements InstallSchemaInterface
{
    public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $setup->startSetup();

        $table = $setup->getConnection()
            ->newTable($setup->getTable('vendor_module_example'))
            ->addColumn(
                'entity_id',
                \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
                null,
                ['identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true],
                'Entity ID'
            )
            ->addColumn(
                'title',
                \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                255,
                ['nullable' => false],
                'Title'
            )
            ->addColumn(
                'status',
                \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT,
                null,
                ['nullable' => false, 'default' => 0],
                'Status'
            )
            ->setComment('Vendor Module Example Table');

        $setup->getConnection()->createTable($table);

        $setup->endSetup();
    }
}

InstallData

Inserts default data after the schema is installed:

<?php
namespace Vendor\Module\Setup;

use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

class InstallData implements InstallDataInterface
{
    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $setup->startSetup();

        $setup->getConnection()->insert(
            $setup->getTable('vendor_module_example'),
            [
                'title' => 'Default Item',
                'status' => 1
            ]
        );

        $setup->endSetup();
    }
}

View Directory

The view/ directory contains frontend and adminhtml resources.

view/
  frontend/
    layout/       # Layout XML files for the storefront
    templates/    # PHTML template files
    web/          # CSS, JS, images, fonts
    requirejs-config.js  # RequireJS configuration
    email/        # Email template HTML files
  adminhtml/
    layout/       # Layout XML files for the admin panel
    templates/    # Admin panel PHTML templates
    web/          # Admin panel static assets

Template Path Convention

When you reference a template in layout XML or block, the path uses the format Vendor_Module::path/to/template.phtml. This maps to view/frontend/templates/path/to/template.phtml in the module or theme.

i18n Directory

The i18n/ directory contains translation CSV files. Each file maps source strings to translated strings for a specific locale.

en_US.csv:
"Submit","Submit"
"Add to Cart","Add to Cart"

de_DE.csv:
"Submit","Absenden"
"Add to Cart","In den Warenkorb"

Api Directory

The Api/ directory contains interfaces that define service contracts. Service contracts are the public API of your module — they define how other modules interact with your code.

<?php
namespace Vendor\Module\Api;

interface ExampleRepositoryInterface
{
    public function save(\Vendor\Module\Api\Data\ExampleInterface $example);
    public function getById(int $id);
    public function delete(\Vendor\Module\Api\Data\ExampleInterface $example);
}

Enabling a Module

Once your module's registration.php and etc/module.xml are in place, enable it with these commands:

# Enable the module
bin/magento module:enable Vendor_Module

# Run setup upgrade to execute install scripts
bin/magento setup:upgrade

# Compile dependency injection configuration
bin/magento setup:di:compile

# Deploy static content for production mode
bin/magento setup:static-content:deploy -f

# Clear all caches
bin/magento cache:clean
bin/magento cache:flush

Checking Module Status

# List all enabled and disabled modules
bin/magento module:status

# Check if a specific module is enabled
bin/magento module:status Vendor_Module

Disabling a Module

bin/magento module:disable Vendor_Module
bin/magento setup:upgrade
bin/magento cache:flush

Module disabling is rarely needed for custom modules. It is more common for third-party extensions that conflict with each other.

Module Dependencies

Modules declare dependencies in module.xml using the <sequence> element. Magento processes modules in sequence order:

<module name="Vendor_Module" setup_version="1.0.0">
    <sequence>
        <module name="Magento_Sales"/>
        <module name="Magento_Customer"/>
    </sequence>
</module>

This ensures that Magento_Sales and Magento_Customer are loaded and their schemas are installed before Vendor_Module runs its setup scripts. If your module uses sales entities or customer data, declare the dependency explicitly.

Common Mistakes

  1. Mismatched module names. The module name in registration.php, module.xml, and the directory structure must all match exactly. Vendor_Module in registration.php but vendor_module in module.xml causes a fatal error. The format is always VendorName_ModuleName with capital first letters and an underscore separator.

  2. Missing setup scripts after schema changes. When you add a new database table or modify an existing one, you need to either create an UpgradeSchema script or increment the setup_version in module.xml. Without this, Magento does not know that schema changes are pending.

  3. Forgetting setup:upgrade after enabling. Enabling a module only registers it. You must run bin/magento setup:upgrade to execute install scripts that create database tables and insert default data. Skipping this step results in errors when the module tries to access missing tables.

  4. Registering classes with wrong namespace. The PHP namespace must match the directory structure. Vendor\Module\Block\Greeting requires the file app/code/Vendor/Module/Block/Greeting.php. A mismatch causes a "Class not found" error that is hard to debug.

  5. Putting everything in one module. A module should have a single responsibility. Do not create a "Utils" module that handles loyalty points, SEO, and shipping all together. Split functionality into focused modules. This makes upgrades, testing, and debugging much easier.

Practice Questions

  1. What are the two required files for a Magento module? Answer: registration.php (registers the module with ComponentRegistrar) and etc/module.xml (declares the module name, setup version, and dependencies). Without these two files, Magento does not recognize the module.

  2. What is the purpose of the setup_version attribute in module.xml? Answer: The setup_version tracks the current schema and data version of the module. Magento compares this version against the database record to determine which setup scripts to execute. Incrementing the version triggers upgrade scripts defined in Setup/UpgradeSchema.php or Setup/UpgradeData.php.

  3. What does the <sequence> element in module.xml do? Answer: The <sequence> element declares dependencies on other modules. It tells Magento to load and initialize the specified modules before your module. This ensures that database tables, configuration, and classes from dependent modules are available when your module runs.

  4. Challenge: Create a complete module called Vendor_ProductAlert that adds a custom database table vendor_productalert_notification with columns for entity_id, product_id, customer_id, created_at, and notified_at. Include the registration.php, module.xml, InstallSchema.php, and a block class with a corresponding template. Enable the module and verify the table exists in the database using SHOW TABLES;.

FAQ

What is the difference between `app/code` and `vendor/` for modules?

app/code is for custom and third-party modules specific to your project. These are typically not managed by Composer. vendor/ is for Composer-installed packages, including Magento core modules and third-party extensions installed via composer require. Your custom modules always go in app/code/VendorName/ModuleName/.

Can a module have both frontend and adminhtml controllers?

Yes. Frontend controllers go in Controller/ directly (or subdirectories) and use the standard router. Admin controllers go in Controller/Adminhtml/ and use the admin router. Both are registered in their respective routes.xml files. This separation keeps frontend and admin logic properly organized.

What happens when I run `setup:upgrade`?

The command compares the setup_version in module.xml against the database record in setup_module table. If the module is new, it runs InstallSchema and InstallData. If the version changed, it runs UpgradeSchema and UpgradeData for each version between the old and new values. This is how Magento applies incremental database changes safely.

Do all modules need a `Setup/` directory?

No. Modules that do not create database tables or insert data do not need setup scripts. Many simple modules only add layout XML, plugins, or configuration and never touch the database. The Block/, Controller/, and Model/ directories are also optional depending on what the module does.

How do I override a core module's functionality instead of creating a new module?

You have several options without creating a new module: preferences in di.xml (replace a class), plugins (intercept public methods), events (observe actions), or layout XML overrides in your theme. Create a new module only when you need significant new functionality that does not fit into an existing module.

Mini Project

Your task: Create the complete module skeleton for a custom "Customer Referral" module.

  1. Create the directory app/code/Vendor/CustomerReferral/.
  2. Create registration.php with ComponentRegistrar::register.
  3. Create etc/module.xml with dependencies on Magento_Customer and Magento_Sales.
  4. Create etc/config.xml with a default configuration value customer_referral/general/reward_points = 100.
  5. Create etc/di.xml with a preference for a Repository interface.
  6. Create Block/Referral.php that extends Template.
  7. Create Model/Referral.php, Model/ResourceModel/Referral.php, and Model/ResourceModel/Referral/Collection.php.
  8. Create Setup/InstallSchema.php with a vendor_customerreferral_referral table.
  9. Enable the module and verify it appears in bin/magento module:status.

This exercise covers the most common module structure that you will use in almost every Magento project. A well-structured module is easier to maintain, debug, and share with other developers.

What's Next

Now that you understand module structure, the next step is learning how Magento wires classes together:

Continue to Lesson 26: Dependency Injection — di.xml, preferences, type configuration, and virtual types.

Related lessons:

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro