- We offer certified developers to hire.
- We’ve performed 500+ Web/App/eCommerce projects.
- Our clientele is 1000+.
- Free quotation on your project.
- We sign NDA for the security of your projects.
- Three months warranty on code developed by us.
Magento (Adobe Commerce) is one of the most powerful ecommerce platforms in the world trusted by enterprise brands, global retailers, and high-growth ecommerce businesses. But one of the main reasons Magento stands apart is its extensibility. You can customize almost anything inside Magento using extensions.
If you want to add a new feature, integrate a third-party system, create custom workflows, add UI components, automate processes, or enhance the checkout — you can do it by building a Magento extension.
This guide is your step-by-step, beginner-to-advanced blueprint for understanding how to create a Magento extension from scratch — even if you’ve never built one before.
A Magento extension (also called a module) is a set of files — code, configuration, and sometimes frontend assets — that adds new functionality to Magento or modifies existing behavior without changing core files.
Important:
You should never modify Magento’s core code directly.
Extensions are the safe and maintainable way to customize Magento.
| Use Case | Example |
| UI / UX Enhancement | Custom product tabs, banners, layout changes |
| Business Logic | Custom shipping/delivery rule, dynamic pricing engine |
| System Integration | ERP, CRM, POS, Payment Gateway, SMS Gateway |
| Marketing | Loyalty reward point systems, Refer & Earn module |
| Operations | Auto invoicing, warehouse management workflows |
There are thousands of ready-made extensions in Adobe Marketplace — but they don’t always meet your exact needs.
You create a custom extension when:
Building your own extension ensures:
✅ Stability
✅ Maintainability
✅ Performance
✅ Future upgrade compatibility
Before writing code, you must understand the Magento module structure.
A module lives under:
app/code/<VendorName>/<ModuleName>/
Example:
app/code/StoreTools/CustomMsg/
Here:
| File / Folder | Purpose |
| registration.php | Registers the module with Magento |
| etc/module.xml | Defines module version & load sequence |
| etc/* configs | Routing, DI, events, ACL, etc. |
| Block/ | PHP logic to send data to frontend |
| Controller/ | Handles requests |
| Model/ | Business logic & database interaction |
| view/ | Templates, layouts, JS, CSS, UI Components |
Before writing code, ensure the environment is correctly configured.
| Component | Recommended Version |
| PHP | 7.4 – 8.2 (depending on Magento version) |
| Magento (Adobe Commerce or Open Source) | 2.4.x |
| Web Server | Apache or Nginx |
| Database | MySQL/MariaDB |
| Composer | Latest Stable |
| IDE | PhpStorm / VS Code |
We will create a simple module that displays a custom message on the frontend.
This helps understand the core setup.
Go to your Magento installation directory and run:
mkdir -p app/code/StoreTools/CustomMsg/etc
Your folder should now look like:
app/
code/
StoreTools/
CustomMsg/
etc/
Create file:
app/code/StoreTools/CustomMsg/registration.php
<?php
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(
ComponentRegistrar::MODULE,
‘StoreTools_CustomMsg’,
__DIR__
);
This file registers the module inside Magento.
File:
app/code/StoreTools/CustomMsg/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=”StoreTools_CustomMsg” setup_version=”1.0.0″ />
</config>
This file defines module metadata, including version.
Run:
php bin/magento setup:upgrade
php bin/magento cache:flush
Check module status:
php bin/magento module:status
You should see:
StoreTools_CustomMsg: enabled
???? Your first module is now registered and active — but does nothing yet.
We will:
app/code/StoreTools/CustomMsg/Block/Display.php
<?php
namespace StoreTools\CustomMsg\Block;
use Magento\Framework\View\Element\Template;
class Display extends Template
{
public function getCustomMessage()
{
return “Hello! This is your first Magento custom extension working successfully!”;
}
}
app/code/StoreTools/CustomMsg/view/frontend/layout/default.xml
<?xml version=”1.0″?>
<page xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:noNamespaceSchemaLocation=”urn:magento:framework:View/Layout/etc/page_configuration.xsd”>
<body>
<block class=”StoreTools\CustomMsg\Block\Display” name=”custom.msg” template=”StoreTools_CustomMsg::message.phtml”/>
</body>
</page>
app/code/StoreTools/CustomMsg/view/frontend/templates/message.phtml
<p style=”padding:10px;background:#fafafa;border:1px solid #ddd;margin:20px 0;”>
<?= $block->getCustomMessage(); ?>
</p>
php bin/magento cache:flush
Now open any frontend page → You’ll see your custom message.
✅ Your first Magento extension is officially working!
This is the foundation for building any advanced module.
Deep Dive: Controllers, Models, Database, Admin Settings, Plugins & Events
In Part 1, we created a basic Magento extension and displayed a custom message on the frontend.
Now, we will move into core functional development, where real Magento extensions start taking shape.
This part covers:
By the end of Part 2, you’ll understand how to build powerful, logic-based extensions that interact with data, UI, events, and admin settings.
Controllers handle HTTP requests — e.g., a user visits /custommsg/display.
app/code/StoreTools/CustomMsg/Controller/Index/
app/code/StoreTools/CustomMsg/Controller/Index/Show.php
<?php
namespace StoreTools\CustomMsg\Controller\Index;
use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\Controller\ResultFactory;
class Show extends Action
{
public function __construct(Context $context)
{
return parent::__construct($context);
}
public function execute()
{
$resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE);
return $resultPage;
}
}
Routes tell Magento which URL maps to your controller.
Create:
app/code/StoreTools/CustomMsg/etc/frontend/routes.xml
<?xml version=”1.0″?>
<routes xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:noNamespaceSchemaLocation=”urn:magento:framework:App/etc/routes.xsd”>
<route id=”custommsg” frontName=”custommsg”>
<module name=”StoreTools_CustomMsg”/>
</route>
</routes>
Now, visiting:
https://yourstore.com/custommsg/index/show
will display a blank page (we’ll add layout next).
app/code/StoreTools/CustomMsg/view/frontend/layout/custommsg_index_show.xml
<?xml version=”1.0″?>
<page xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:noNamespaceSchemaLocation=”urn:magento:framework:View/Layout/etc/page_configuration.xsd”>
<body>
<block class=”StoreTools\CustomMsg\Block\Display” template=”StoreTools_CustomMsg::message.phtml”/>
</body>
</page>
Now refresh → custom message appears on a dedicated page.
✅ Frontend routing is now complete.
Models handle business logic.
Resource models handle database communication.
We will store custom messages in a database.
Model/
ResourceModel/
ResourceModel/Message/
app/code/StoreTools/CustomMsg/Model/Message.php
<?php
namespace StoreTools\CustomMsg\Model;
use Magento\Framework\Model\AbstractModel;
class Message extends AbstractModel
{
protected function _construct()
{
$this->_init(\StoreTools\CustomMsg\Model\ResourceModel\Message::class);
}
}
app/code/StoreTools/CustomMsg/Model/ResourceModel/Message.php
<?php
namespace StoreTools\CustomMsg\Model\ResourceModel;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
class Message extends AbstractDb
{
protected function _construct()
{
$this->_init(‘custom_messages’, ‘message_id’);
}
}
app/code/StoreTools/CustomMsg/Model/ResourceModel/Message/Collection.php
<?php
namespace StoreTools\CustomMsg\Model\ResourceModel\Message;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
class Collection extends AbstractCollection
{
protected $_idFieldName = ‘message_id’;
protected function _construct()
{
$this->_init(
\StoreTools\CustomMsg\Model\Message::class,
\StoreTools\CustomMsg\Model\ResourceModel\Message::class
);
}
}
✅ Model layer is ready.
File:
app/code/StoreTools/CustomMsg/etc/db_schema.xml
<?xml version=”1.0″?>
<schema xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:noNamespaceSchemaLocation=”urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd”>
<table name=”custom_messages” resource=”default” engine=”innodb” comment=”Custom Messages Table”>
<column name=”message_id” xsi:type=”int” nullable=”false” identity=”true” unsigned=”true” comment=”ID” />
<column name=”message_text” xsi:type=”text” nullable=”false” comment=”Custom Message” />
<constraint xsi:type=”primary” referenceId=”PRIMARY”>
<column name=”message_id”/>
</constraint>
</table>
</schema>
Run:
php bin/magento setup:upgrade
✅ Table custom_messages is created.
Allows store owners to control module behavior via admin.
Create:
app/code/StoreTools/CustomMsg/etc/adminhtml/system.xml
<?xml version=”1.0″?>
<config xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:noNamespaceSchemaLocation=”urn:magento:module:Magento_Config:etc/system_file.xsd”>
<system>
<section id=”custom_msg” translate=”label” sortOrder=”10″ showInDefault=”1″ showInWebsite=”1″ showInStore=”1″>
<label>Custom Message Settings</label>
<tab>general</tab>
<group id=”general” translate=”label” sortOrder=”10″>
<label>General Config</label>
<field id=”enabled” translate=”label” type=”select” sortOrder=”10″ showInDefault=”1″>
<label>Enable Message?</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
</group>
</section>
</system>
</config>
Now, admin can enable/disable message in:
Stores → Configuration → General → Custom Message Settings
Inject Model in Block:
protected $messageModel;
public function __construct(
Template\Context $context,
\StoreTools\CustomMsg\Model\Message $messageModel,
array $data = []
) {
$this->messageModel = $messageModel;
parent::__construct($context, $data);
}
Example: log every time product is viewed.
Create file:
etc/frontend/events.xml
<?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=”controller_action_postdispatch_catalog_product_view”>
<observer name=”log_product_view” instance=”StoreTools\CustomMsg\Observer\LogProductView” />
</event>
</config>
Observer:
Observer/LogProductView.php
<?php
namespace StoreTools\CustomMsg\Observer;
use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
class LogProductView implements ObserverInterface
{
public function execute(Observer $observer)
{
// Code to log product views
}
}
Modify behavior of any public method without overriding.
Example: modify product name:
etc/di.xml
<type name=”Magento\Catalog\Model\Product”>
<plugin name=”custom_name_modifier” type=”StoreTools\CustomMsg\Plugin\ProductName” />
</type>
Plugin:
Plugin/ProductName.php
<?php
namespace StoreTools\CustomMsg\Plugin;
class ProductName
{
public function afterGetName($subject, $result)
{
return “[Custom] ” . $result;
}
}
✅ Product names now show with prefix.
In Part 2, we established the core functional foundation of the Magento extension — including database models, event observers, DI usage, and admin configuration.
Now we move into one of the most important areas of Magento extension development:
➡️ Creating Admin User Interface (UI Components)
This includes:
By the end of this part, your extension will have a full admin panel management interface — just like any professional Magento module.
Your module needs a menu entry inside the admin panel.
Create the menu file:
app/code/StoreTools/CustomMsg/etc/adminhtml/menu.xml
<?xml version=”1.0″?>
<config xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:noNamespaceSchemaLocation=”urn:magento:framework:Menu/etc/menu.xsd”>
<menu>
<add id=”StoreTools_CustomMsg::main_menu”
title=”Custom Messages”
module=”StoreTools_CustomMsg”
sortOrder=”10″
parent=”Magento_Backend::content” />
<add id=”StoreTools_CustomMsg::messages”
title=”Manage Messages”
module=”StoreTools_CustomMsg”
sortOrder=”20″
parent=”StoreTools_CustomMsg::main_menu”
action=”custommsg/message/index” />
</menu>
</config>
Now you have:
Admin → Content → Custom Messages → Manage Messages
Permissions determine who can view or edit your module.
app/code/StoreTools/CustomMsg/etc/acl.xml
<?xml version=”1.0″?>
<acl xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:noNamespaceSchemaLocation=”urn:magento:framework:Acl/etc/acl.xsd”>
<resource id=”Magento_Backend::admin”>
<resource id=”StoreTools_CustomMsg::main_menu” title=”Custom Messages” sortOrder=”10″>
<resource id=”StoreTools_CustomMsg::messages” title=”Manage Messages” />
</resource>
</resource>
</acl>
✅ Admin users must now have permissions to access module pages.
app/code/StoreTools/CustomMsg/Controller/Adminhtml/Message/Index.php
<?php
namespace StoreTools\CustomMsg\Controller\Adminhtml\Message;
use Magento\Backend\App\Action;
use Magento\Framework\View\Result\PageFactory;
class Index extends Action
{
const ADMIN_RESOURCE = ‘StoreTools_CustomMsg::messages’;
protected $pageFactory;
public function __construct(PageFactory $pageFactory, Action\Context $context)
{
$this->pageFactory = $pageFactory;
parent::__construct($context);
}
public function execute()
{
$resultPage = $this->pageFactory->create();
$resultPage->getConfig()->getTitle()->prepend(__(‘Manage Custom Messages’));
return $resultPage;
}
}
app/code/StoreTools/CustomMsg/view/adminhtml/layout/custommsg_message_index.xml
<?xml version=”1.0″?>
<page xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:noNamespaceSchemaLocation=”urn:magento:framework:View/Layout/etc/page_configuration.xsd”>
<body>
<uiComponent name=”custommsg_message_listing”/>
</body>
</page>
app/code/StoreTools/CustomMsg/view/adminhtml/ui_component/custommsg_message_listing.xml
<?xml version=”1.0″?>
<uiComponent xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:noNamespaceSchemaLocation=”urn:magento:module:Magento_Ui:etc/ui_configuration.xsd”
name=”custommsg_message_listing”>
<dataSource name=”custommsg_message_listing_data_source”>
<argument name=”dataProvider” xsi:type=”string”>StoreTools\CustomMsg\Ui\DataProvider\MessageDataProvider</argument>
<argument name=”primaryFieldName” xsi:type=”string”>message_id</argument>
<argument name=”requestFieldName” xsi:type=”string”>message_id</argument>
</dataSource>
<columns name=”message_columns”>
<column name=”message_id”>
<settings>
<label>ID</label>
</settings>
</column>
<column name=”message_text”>
<settings>
<label>Message</label>
</settings>
</column>
</columns>
</uiComponent>
app/code/StoreTools/CustomMsg/Ui/DataProvider/MessageDataProvider.php
<?php
namespace StoreTools\CustomMsg\Ui\DataProvider;
use Magento\Ui\DataProvider\AbstractDataProvider;
use StoreTools\CustomMsg\Model\ResourceModel\Message\CollectionFactory;
class MessageDataProvider extends AbstractDataProvider
{
protected $collection;
public function __construct(
$name,
$primaryFieldName,
$requestFieldName,
CollectionFactory $collectionFactory,
array $meta = [],
array $data = []
) {
$this->collection = $collectionFactory->create();
parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
}
}
✅ You now have a fully working Admin Grid listing messages from the database.
We will complete this in Part 4 (since CRUD UI itself is extensive).
Add this to your grid UI component:
app/code/StoreTools/CustomMsg/view/adminhtml/ui_component/custommsg_message_listing.xml
Inside <columns>, add:
<actionsColumn name=”actions” class=”Magento\Ui\Component\Listing\Columns\Actions”>
<settings>
<label>Actions</label>
</settings>
<action name=”edit”>
<settings>
<label>Edit</label>
<url path=”custommsg/message/edit”/>
<param name=”message_id”>message_id</param>
</settings>
</action>
</actionsColumn>
Add toolbar button:
<listingToolbar name=”listing_top”>
<button name=”add”>
<settings>
<label>Add New Message</label>
<url path=”custommsg/message/new”/>
<class>primary</class>
</settings>
</button>
</listingToolbar>
app/code/StoreTools/CustomMsg/Controller/Adminhtml/Message/Edit.php
<?php
namespace StoreTools\CustomMsg\Controller\Adminhtml\Message;
use Magento\Backend\App\Action;
use Magento\Framework\View\Result\PageFactory;
use StoreTools\CustomMsg\Model\MessageFactory;
class Edit extends Action
{
const ADMIN_RESOURCE = ‘StoreTools_CustomMsg::messages’;
protected $pageFactory;
protected $messageFactory;
public function __construct(PageFactory $pageFactory, MessageFactory $messageFactory, Action\Context $context)
{
$this->pageFactory = $pageFactory;
$this->messageFactory = $messageFactory;
parent::__construct($context);
}
public function execute()
{
$id = $this->getRequest()->getParam(‘message_id’);
$model = $this->messageFactory->create();
if ($id) {
$model->load($id);
}
$resultPage = $this->pageFactory->create();
$resultPage->getConfig()->getTitle()->prepend($id ? “Edit Message” : “New Message”);
return $resultPage;
}
}
app/code/StoreTools/CustomMsg/view/adminhtml/ui_component/custommsg_message_form.xml
<?xml version=”1.0″?>
<form xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:noNamespaceSchemaLocation=”urn:magento:module:Magento_Ui:etc/ui_configuration.xsd”
name=”custommsg_message_form”>
<dataSource name=”custommsg_message_form_data_source”>
<argument name=”dataProvider” xsi:type=”string”>StoreTools\CustomMsg\Ui\DataProvider\Form\MessageFormDataProvider</argument>
<argument name=”primaryFieldName” xsi:type=”string”>message_id</argument>
<argument name=”requestFieldName” xsi:type=”string”>message_id</argument>
</dataSource>
<formFields>
<field name=”message_text”>
<settings>
<dataType>text</dataType>
<label>Message Text</label>
<required>true</required>
</settings>
</field>
</formFields>
</form>
app/code/StoreTools/CustomMsg/Controller/Adminhtml/Message/Save.php
<?php
namespace StoreTools\CustomMsg\Controller\Adminhtml\Message;
use Magento\Backend\App\Action;
use StoreTools\CustomMsg\Model\MessageFactory;
class Save extends Action
{
const ADMIN_RESOURCE = ‘StoreTools_CustomMsg::messages’;
protected $messageFactory;
public function __construct(MessageFactory $messageFactory, Action\Context $context)
{
$this->messageFactory = $messageFactory;
parent::__construct($context);
}
public function execute()
{
$data = $this->getRequest()->getPostValue();
$id = $data[‘message_id’] ?? null;
$model = $this->messageFactory->create();
if ($id) $model->load($id);
$model->setData($data)->save();
$this->messageManager->addSuccessMessage(__(‘Message saved successfully.’));
return $this->_redirect(‘*/*/index’);
}
}
✅ You can now Add, Edit, Save data in the admin panel.
Deleting one record:
custommsg/message/delete
Mass delete in grid:
Add this to grid:
<massaction name=”massaction”>
<action name=”delete” type=”delete” label=”Delete” url=”custommsg/message/massDelete”/>
</massaction>
app/code/StoreTools/CustomMsg/etc/crontab.xml
<crontab>
<group id=”default”>
<job name=”cleanup_messages” instance=”StoreTools\CustomMsg\Cron\Cleanup” method=”execute”>
<schedule>0 * * * *</schedule>
</job>
</group>
</crontab>
Cron class:
public function execute()
{
// Example: remove messages older than 30 days
}
REST:
app/code/StoreTools/CustomMsg/etc/webapi.xml
<route url=”/V1/custom/messages” method=”GET”>
<service class=”StoreTools\CustomMsg\Api\MessageRepositoryInterface” method=”getList”/>
<resources>
<resource ref=”anonymous”/>
</resources>
</route>
GraphQL:
etc/graphql/schema.graphqls
type Message {
message_id: Int
message_text: String
}
type Query {
customMessages: [Message] @resolver(class: “StoreTools\\CustomMsg\\Model\\Resolver\\Messages”)
}
✅ Your module now supports headless storefronts and mobile apps.
To support store-view level custom messaging, use:
<column … store=”true”/>
And in PHP:
$model->setStoreId($storeId);
You have now developed a full Magento extension, including:
| Module Feature | Completed |
| Core module structure | ✅ |
| Frontend display & routing | ✅ |
| Database schema & models | ✅ |
| Admin settings & ACL | ✅ |
| Admin Grid + Forms (CRUD) | ✅ |
| Event observers & plugins | ✅ |
| Cron jobs & scheduled automation | ✅ |
| REST & GraphQL API support | ✅ |
| Multi-store scope support | ✅ |
If you ever need professional Magento extension development, architectural consulting, or enterprise-level optimization, consider partnering with Abbacus Technologies, a leading Magento development company:
You now have the knowledge to create, extend, optimize, secure, and deploy professional-grade Magento extensions.