Modules are the fundamental building blocks of Magento 2. This guide covers everything you need to know about creating, registering, and managing custom modules.
- What is a Module?
- Module Structure
- Creating Your First Module
- Module Registration
- Module Declaration
- Module Dependencies
- Enabling and Disabling Modules
- Best Practices
- Practical Examples
A module is a structural element of Magento 2 that contains all the logic related to a specific business feature. Magento 2 itself is built from modules - the core functionality is divided into over 200 modules.
- Self-contained: Contains all code for a specific feature
- Reusable: Can be installed in different Magento instances
- Modular: Can be enabled/disabled independently
- Upgradeable: Supports versioning and upgrades
- Configurable: Can have dependencies on other modules
- Core Modules: Located in
vendor/magento/module-* - Custom Modules: Located in
app/code/{Vendor}/{ModuleName} - Third-party Modules: Installed via Composer in
vendor/
A typical Magento 2 module follows this directory structure:
app/code/Vendor/ModuleName/
├── Api/ # Service contracts (interfaces)
│ ├── Data/ # Data interfaces
│ └── ProductRepositoryInterface.php
├── Block/ # Block classes (View layer)
│ └── Product/
│ └── View.php
├── Console/ # CLI commands
│ └── Command/
│ └── CustomCommand.php
├── Controller/ # Controllers (handle HTTP requests)
│ ├── Adminhtml/ # Admin controllers
│ └── Index/ # Frontend controllers
│ └── Index.php
├── Cron/ # Cron job classes
│ └── CustomCron.php
├── etc/ # Configuration files
│ ├── adminhtml/ # Admin-specific config
│ │ ├── menu.xml
│ │ ├── routes.xml
│ │ └── system.xml
│ ├── frontend/ # Frontend-specific config
│ │ └── routes.xml
│ ├── acl.xml # Access Control List
│ ├── config.xml # Default config values
│ ├── crontab.xml # Cron configuration
│ ├── di.xml # Dependency injection
│ ├── events.xml # Event observers
│ ├── module.xml # Module declaration
│ └── webapi.xml # Web API routes
├── Helper/ # Helper classes (utility functions)
│ └── Data.php
├── i18n/ # Translation files
│ └── en_US.csv
├── Model/ # Business logic layer
│ ├── ResourceModel/ # Database operations
│ │ ├── Product.php
│ │ └── Product/
│ │ └── Collection.php
│ └── Product.php
├── Observer/ # Event observers
│ └── ProductSaveObserver.php
├── Plugin/ # Plugins/Interceptors
│ └── ProductPlugin.php
├── Setup/ # Installation/upgrade scripts
│ ├── InstallData.php
│ ├── InstallSchema.php
│ ├── UpgradeData.php
│ ├── UpgradeSchema.php
│ ├── Patch/ # Declarative schema patches
│ │ ├── Data/
│ │ └── Schema/
│ └── Recurring.php
├── Test/ # Unit and integration tests
│ ├── Unit/
│ └── Integration/
├── Ui/ # UI components
│ └── Component/
├── view/ # View layer files
│ ├── adminhtml/ # Admin templates/layouts
│ │ ├── layout/
│ │ ├── templates/
│ │ └── ui_component/
│ ├── frontend/ # Frontend templates/layouts
│ │ ├── layout/
│ │ ├── templates/
│ │ ├── web/
│ │ │ ├── css/
│ │ │ ├── js/
│ │ │ └── images/
│ │ └── requirejs-config.js
│ └── base/ # Shared between admin/frontend
├── composer.json # Composer package definition
└── registration.php # Module registration
Every module must have these two files:
- registration.php - Registers the module with Magento
- etc/module.xml - Declares the module and its version
Let's create a simple "Hello World" module step by step.
# Navigate to Magento root
cd /path/to/magento
# Create module directories
mkdir -p app/code/Vendor/HelloWorld/etcNaming Convention:
- Vendor: Your company/organization name (e.g.,
Acme,MyCompany) - ModuleName: The module name in PascalCase (e.g.,
HelloWorld,ProductReview)
Create app/code/Vendor/HelloWorld/registration.php:
<?php
/**
* Copyright © Vendor. All rights reserved.
*/
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(
ComponentRegistrar::MODULE,
'Vendor_HelloWorld',
__DIR__
);Explanation:
ComponentRegistrar::MODULE- Indicates this is a module (not theme or language)'Vendor_HelloWorld'- Module name inVendor_ModuleNameformat__DIR__- Current directory path
Create app/code/Vendor/HelloWorld/etc/module.xml:
<?xml version="1.0"?>
<!--
/**
* Copyright © Vendor. All rights reserved.
*/
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Vendor_HelloWorld" setup_version="1.0.0">
<!-- Module dependencies go here -->
</module>
</config>Explanation:
name="Vendor_HelloWorld"- Module name (must match registration.php)setup_version="1.0.0"- Module version (deprecated in 2.3+, but still used)
# Enable the module
bin/magento module:enable Vendor_HelloWorld
# Run setup upgrade to register the module
bin/magento setup:upgrade
# Clear cache
bin/magento cache:flush
# Verify module is enabled
bin/magento module:status Vendor_HelloWorldExpected Output:
Module is enabled
The registration.php file registers the module with Magento's component registration system.
<?php
/**
* Copyright © [Year] [Vendor]. All rights reserved.
*/
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(
ComponentRegistrar::MODULE, // Component type
'Vendor_ModuleName', // Component name
__DIR__ // Component path
);Magento supports multiple component types:
ComponentRegistrar::MODULE // For modules
ComponentRegistrar::THEME // For themes
ComponentRegistrar::LANGUAGE // For language packs
ComponentRegistrar::LIBRARY // For libraries-
Magento scans the following directories during bootstrap:
app/code/*/*/registration.phpvendor/*/*/registration.phpapp/design/*/*/*/registration.php
-
Each
registration.phpfile registers its component -
The component list is cached for performance
❌ Wrong module name format:
// WRONG
ComponentRegistrar::register(
ComponentRegistrar::MODULE,
'VendorHelloWorld', // Missing underscore
__DIR__
);✅ Correct:
ComponentRegistrar::register(
ComponentRegistrar::MODULE,
'Vendor_HelloWorld', // Correct format
__DIR__
);The etc/module.xml file declares the module's metadata and dependencies.
<?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_ModuleName" setup_version="1.0.0"/>
</config>Note: In Magento 2.3+, setup_version is deprecated in favor of composer.json version. However, it's still commonly used.
<module name="Vendor_ModuleName" setup_version="1.0.0"/>Version Format: MAJOR.MINOR.PATCH
- MAJOR: Incompatible API changes
- MINOR: Backward-compatible functionality additions
- PATCH: Backward-compatible bug fixes
<?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_CustomModule" setup_version="1.2.5">
<sequence>
<module name="Magento_Catalog"/>
<module name="Magento_Customer"/>
<module name="Vendor_AnotherModule"/>
</sequence>
</module>
</config>Dependencies ensure modules load in the correct order.
Use the <sequence> node in module.xml:
<module name="Vendor_CustomModule">
<sequence>
<module name="Magento_Catalog"/>
<module name="Magento_Customer"/>
</sequence>
</module>What this means:
Vendor_CustomModuledepends onMagento_CatalogandMagento_Customer- Those modules will be loaded before
Vendor_CustomModule - If any dependency is missing or disabled, this module won't load
<sequence>
<module name="Magento_Catalog"/>
</sequence>- Module won't work without this dependency
- Used for modules you extend or modify
{
"suggest": {
"magento/module-catalog": "For product features"
}
}- Module can work without it
- Used for optional integrations
If you're creating a module that adds custom attributes to products:
<?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_ProductAttributes" setup_version="1.0.0">
<sequence>
<!-- Required because we're extending product functionality -->
<module name="Magento_Catalog"/>
<module name="Magento_Eav"/>
</sequence>
</module>
</config>❌ AVOID circular dependencies:
Module A depends on Module B, and Module B depends on Module A.
This will cause errors. Always ensure one-way dependency chains.
# Check specific module
bin/magento module:status Vendor_ModuleName
# List all modules
bin/magento module:status
# List only enabled modules
bin/magento module:status --enabled
# List only disabled modules
bin/magento module:status --disabled# Enable single module
bin/magento module:enable Vendor_ModuleName
# Enable multiple modules
bin/magento module:enable Vendor_ModuleOne Vendor_ModuleTwo
# Enable and run setup upgrade
bin/magento module:enable Vendor_ModuleName && bin/magento setup:upgrade# Disable single module
bin/magento module:disable Vendor_ModuleName
# Disable multiple modules
bin/magento module:disable Vendor_ModuleOne Vendor_ModuleTwo
# Disable and clear generated code
bin/magento module:disable Vendor_ModuleName && bin/magento setup:upgrade# Clear all cache
bin/magento cache:flush
# Or specific cache types
bin/magento cache:clean configModule status is stored in app/etc/config.php:
<?php
return [
'modules' => [
'Magento_AdminAnalytics' => 1, // Enabled
'Magento_Store' => 1, // Enabled
'Vendor_HelloWorld' => 1, // Enabled
'Vendor_DisabledModule' => 0, // Disabled
]
];Values:
1= Enabled0= Disabled
You can manually edit app/etc/config.php:
'modules' => [
'Vendor_ModuleName' => 1, // Change to 0 to disable
]Then run:
bin/magento setup:upgrade
bin/magento cache:flush✅ Do:
Vendor: Acme (company name)
Module: ProductReview (PascalCase, descriptive)
Full name: Acme_ProductReview
❌ Don't:
Vendor: mycompany (lowercase)
Module: pr (too short)
Full name: MyCompany-ProductReview (wrong separator)
Keep modules focused:
- ✅ One module for product reviews
- ✅ One module for custom shipping method
- ❌ One module for "everything custom"
Organize files by responsibility:
✅ app/code/Vendor/ProductReview/
├── Block/Review/
├── Controller/Review/
└── Model/Review/
❌ app/code/Vendor/ProductReview/
├── AllBlocks.php
└── AllControllers.php
Always include:
- Copyright notice in files
- README.md with module description
- Comments for complex logic
Update version when:
- Adding database changes
- Modifying module structure
- Making breaking changes
Only declare necessary dependencies:
- Include in
<sequence>only modules you directly use - Don't declare transitive dependencies
Maintain compatibility:
- Don't remove public methods
- Don't change method signatures
- Use deprecation notices before removal
Purpose: Add a "Featured" flag to products
# Create module structure
mkdir -p app/code/Acme/FeaturedProduct/etcapp/code/Acme/FeaturedProduct/registration.php:
<?php
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(
ComponentRegistrar::MODULE,
'Acme_FeaturedProduct',
__DIR__
);app/code/Acme/FeaturedProduct/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="Acme_FeaturedProduct" setup_version="1.0.0">
<sequence>
<module name="Magento_Catalog"/>
</sequence>
</module>
</config>Enable:
bin/magento module:enable Acme_FeaturedProduct
bin/magento setup:upgrade
bin/magento cache:flushPurpose: Custom checkout that depends on multiple modules
app/code/Acme/CustomCheckout/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="Acme_CustomCheckout" setup_version="1.0.0">
<sequence>
<module name="Magento_Checkout"/>
<module name="Magento_Quote"/>
<module name="Magento_Sales"/>
<module name="Magento_Customer"/>
<module name="Magento_Payment"/>
</sequence>
</module>
</config>app/code/Acme/BlogModule/composer.json:
{
"name": "acme/module-blog",
"description": "Blog functionality for Magento 2",
"type": "magento2-module",
"version": "1.0.0",
"license": "proprietary",
"authors": [
{
"name": "Acme Developer",
"email": "developer@acme.com"
}
],
"require": {
"php": "~8.1.0||~8.2.0||~8.3.0",
"magento/framework": "103.0.*",
"magento/module-cms": "104.0.*",
"magento/module-store": "101.1.*"
},
"autoload": {
"files": [
"registration.php"
],
"psr-4": {
"Acme\\BlogModule\\": ""
}
}
}Before deploying your module, verify:
-
registration.phpcreated with correct module name -
etc/module.xmlcreated with correct version - Module dependencies declared in
<sequence> - Module enabled via CLI
-
setup:upgradeexecuted successfully - No errors in logs (
var/log/) - Module appears in admin: Stores → Configuration → Advanced → Advanced
- Cache cleared and site works correctly
Check:
# 1. Verify file paths are correct
ls -la app/code/Vendor/ModuleName/
# 2. Check registration.php exists
cat app/code/Vendor/ModuleName/registration.php
# 3. Check module.xml exists
cat app/code/Vendor/ModuleName/etc/module.xml
# 4. Run module discovery
bin/magento module:status Vendor_ModuleName# 1. Check for errors
tail -f var/log/system.log
tail -f var/log/exception.log
# 2. Recompile
bin/magento setup:di:compile
# 3. Clear all caches
bin/magento cache:flush
rm -rf generated/* var/cache/* var/page_cache/*
# 4. Upgrade
bin/magento setup:upgradeIf you see "Module X depends on disabled module Y":
# Enable the dependency first
bin/magento module:enable Vendor_DependencyModule
# Then enable your module
bin/magento module:enable Vendor_YourModule
# Run upgrade
bin/magento setup:upgrade- Day 04: Dependency Injection
- Learn about routing and controllers
- Create your first custom functionality