magento-module-development
Included with Lifetime
$97 forever
Build custom Magento 2 modules using dependency injection, plugins, observers, and service contracts to extend core functionality cleanly
platform-magentomagentomagento2moduledependency-injectionservice-contractspluginsobservers
What this skill does
# Magento 2 Module Development
## Overview
Build custom Magento 2 modules using the module architecture, dependency injection (DI), service contracts (interfaces), plugins (interceptors), observers, and the repository pattern. This skill covers the Magento 2 module skeleton, XML-based configuration, the Object Manager and DI container, custom REST/GraphQL API endpoints, and database schema management with `db_schema.xml` (declarative schema).
## When to Use This Skill
- When building a custom module that adds new functionality to a Magento 2 store
- When extending or overriding core Magento behavior using plugins or preferences
- When creating custom REST API or GraphQL endpoints for headless integrations
- When adding custom database tables with declarative schema
- When implementing admin grids, forms, and system configuration
## Core Instructions
1. **Create the module skeleton**
Every Magento 2 module lives in `app/code/Vendor/Module` and requires at minimum two files:
```
app/code/Acme/CustomModule/
├── etc/
│ └── module.xml
├── registration.php
├── Api/
│ └── CustomRepositoryInterface.php
├── Model/
│ ├── CustomRepository.php
│ └── ResourceModel/
├── Controller/
├── Block/
├── view/
│ ├── frontend/
│ └── adminhtml/
└── Setup/
└── Patch/
└── Data/
```
```php
// registration.php
<?php
declare(strict_types=1);
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(
ComponentRegistrar::MODULE,
'Acme_CustomModule',
__DIR__
);
```
```xml
<!-- 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_CustomModule" setup_version="1.0.0">
<sequence>
<module name="Magento_Catalog"/>
<module name="Magento_Sales"/>
</sequence>
</module>
</config>
```
2. **Define service contracts (interfaces) and implement them**
Service contracts ensure your module provides a stable API that other modules and integrations can rely on:
```php
// Api/Data/CustomEntityInterface.php
<?php
declare(strict_types=1);
namespace Acme\CustomModule\Api\Data;
interface CustomEntityInterface
{
const ENTITY_ID = 'entity_id';
const NAME = 'name';
const STATUS = 'status';
const CREATED_AT = 'created_at';
public function getEntityId(): ?int;
public function getName(): string;
public function setName(string $name): self;
public function getStatus(): string;
public function setStatus(string $status): self;
public function getCreatedAt(): ?string;
}
```
```php
// Api/CustomRepositoryInterface.php
<?php
declare(strict_types=1);
namespace Acme\CustomModule\Api;
use Acme\CustomModule\Api\Data\CustomEntityInterface;
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\Api\SearchResultsInterface;
use Magento\Framework\Exception\NoSuchEntityException;
interface CustomRepositoryInterface
{
/**
* @throws NoSuchEntityException
*/
public function getById(int $id): CustomEntityInterface;
public function save(CustomEntityInterface $entity): CustomEntityInterface;
public function delete(CustomEntityInterface $entity): bool;
public function getList(SearchCriteriaInterface $searchCriteria): SearchResultsInterface;
}
```
3. **Implement the Model, ResourceModel, and Repository**
```php
// Model/CustomEntity.php
<?php
declare(strict_types=1);
namespace Acme\CustomModule\Model;
use Acme\CustomModule\Api\Data\CustomEntityInterface;
use Magento\Framework\Model\AbstractModel;
class CustomEntity extends AbstractModel implements CustomEntityInterface
{
protected function _construct(): void
{
$this->_init(\Acme\CustomModule\Model\ResourceModel\CustomEntity::class);
}
public function getEntityId(): ?int
{
return $this->getData(self::ENTITY_ID) ? (int) $this->getData(self::ENTITY_ID) : null;
}
public function getName(): string
{
return (string) $this->getData(self::NAME);
}
public function setName(string $name): CustomEntityInterface
{
return $this->setData(self::NAME, $name);
}
public function getStatus(): string
{
return (string) $this->getData(self::STATUS);
}
public function setStatus(string $status): CustomEntityInterface
{
return $this->setData(self::STATUS, $status);
}
public function getCreatedAt(): ?string
{
return $this->getData(self::CREATED_AT);
}
}
```
```php
// Model/ResourceModel/CustomEntity.php
<?php
declare(strict_types=1);
namespace Acme\CustomModule\Model\ResourceModel;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
class CustomEntity extends AbstractDb
{
protected function _construct(): void
{
$this->_init('acme_custom_entity', 'entity_id');
}
}
```
```php
// Model/CustomRepository.php
<?php
declare(strict_types=1);
namespace Acme\CustomModule\Model;
use Acme\CustomModule\Api\CustomRepositoryInterface;
use Acme\CustomModule\Api\Data\CustomEntityInterface;
use Acme\CustomModule\Model\ResourceModel\CustomEntity as ResourceModel;
use Acme\CustomModule\Model\CustomEntityFactory;
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\Api\SearchResultsInterface;
use Magento\Framework\Api\SearchResultsInterfaceFactory;
use Magento\Framework\Exception\NoSuchEntityException;
class CustomRepository implements CustomRepositoryInterface
{
public function __construct(
private readonly ResourceModel $resourceModel,
private readonly CustomEntityFactory $entityFactory,
private readonly SearchResultsInterfaceFactory $searchResultsFactory,
private readonly \Acme\CustomModule\Model\ResourceModel\CustomEntity\CollectionFactory $collectionFactory
) {}
public function getById(int $id): CustomEntityInterface
{
$entity = $this->entityFactory->create();
$this->resourceModel->load($entity, $id);
if (!$entity->getEntityId()) {
throw new NoSuchEntityException(
__('Entity with ID "%1" does not exist.', $id)
);
}
return $entity;
}
public function save(CustomEntityInterface $entity): CustomEntityInterface
{
$this->resourceModel->save($entity);
return $entity;
}
public function delete(CustomEntityInterface $entity): bool
{
$this->resourceModel->delete($entity);
return true;
}
public function getList(SearchCriteriaInterface $searchCriteria): SearchResultsInterface
{
$collection = $this->collectionFactory->create();
foreach ($searchCriteria->getFilterGroups() as $filterGroup) {
foreach ($filterGroup->getFilters() as $filter) {
$collection->addFieldToFilter(
$filter->getField(),
[$filter->getConditionType() => $filter->getValue()]
);
}
}
$searchResults = $this->searchResultsFactory->create();
$searchResults->setSearchCriteria($searchCriteria);
$searchResults->setItems($collection->getItems());
$searchResults->setTotalCount($collection->getSize());
return $searchResults;
}
}
```
4. **Configure dependency injection witRelated in platform-magento
magento-graphql
IncludedQuery Magento's GraphQL API to build headless storefronts or PWA Studio frontends with products, cart, checkout, and customer operations
platform-magento
magento-multi-store
IncludedConfigure multiple websites and store views in Magento with shared or scoped catalogs, separate URL structures, and store-specific settings
platform-magento
magento-indexing-caching
IncludedSpeed up Magento by managing indexers correctly, configuring Varnish full-page cache, and using Redis for session and object caching
platform-magento