Claude
Skills
Sign in
Back

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 wit

Related in platform-magento