Install
$ agentstack add skill-ddtcorex-dev-skills-hub-magento2-backend-dev ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Magento 2 Backend Developer
This skill covers API development (REST, SOAP, GraphQL), CLI commands, cron jobs, and message queues.
Related Skills
REQUIRED BACKGROUND: Load magento2-dev-core first — it defines the DI, repository, and security patterns (constructor injection, service contracts, escaping, discouraged functions) this skill assumes without repeating.
Pairs with magento2-security-scan when the API/resolver you're building touches authentication, ACL, or user input, and with magento2-performance-audit for queue/consumer and N+1 concerns once the endpoint is built. In a Govard environment, use govard-magento for the CLI/container side (bin/magento, cache, indexers).
REST API
Service Contract Structure
Vendor/Module/
├── Api/
│ ├── ProductRepositoryInterface.php # Declaration
│ └── Data/
│ └── ProductInterface.php # Data entity
└── Model/
└── ProductRepository.php # Implementation
Data Interface
resource->save($product);
return $product;
}
public function getById(int $id): ProductInterface
{
$product = $this->productFactory->create();
$this->resource->load($product, $id);
if (!$product->getId()) {
throw new \Magento\Framework\Exception\NoSuchEntityException(
__('Product with ID %1 does not exist', $id)
);
}
return $product;
}
public function get(SearchCriteriaInterface $searchCriteria): ProductSearchResultsInterface
{
$searchResults = $this->searchResultsFactory->create();
$searchResults->setSearchCriteria($searchCriteria);
$collection = $this->productCollection->create();
$this->applySearchCriteria($collection, $searchCriteria);
$searchResults->setItems($collection->getItems());
$searchResults->setTotalCount($collection->getSize());
return $searchResults;
}
}
WebAPI Configuration
ACL Configuration
GraphQL
Schema Definition
# etc/schema.graphqls
type Query {
products(filter: ProductFilterInput, pageSize: Int = 20, currentPage: Int = 1): Products
@doc(description: "Get products list")
@resolver(class: "Vendor\\Module\\Model\\Resolver\\ProductList")
@cache(cacheable: false)
}
type Mutation {
createProduct(input: ProductInput!): Product
@doc(description: "Create a new product")
@resolver(class: "Vendor\\Module\\Model\\Resolver\\CreateProduct")
@cache(cacheable: false)
}
input ProductFilterInput {
entity_id: FilterTypeInput
name: FilterTypeInput
sku: FilterTypeInput
price: FilterTypeInput
}
type Product {
entity_id: Int
name: String
sku: String
price: Float
}
input ProductInput {
name: String!
sku: String!
price: Float!
}
Resolver Implementation
searchCriteriaBuilder
->setPageSize($args['pageSize'])
->setCurrentPage($args['currentPage'] ?? 1)
->create();
$searchResults = $this->productRepository->get($searchCriteria);
return [
'total_count' => $searchResults->getTotalCount(),
'items' => $this->convertProducts($searchResults->getItems())
];
}
private function convertProducts(array $products): array
{
return array_map(function ($product) {
return [
'entity_id' => $product->getId(),
'name' => $product->getName(),
'sku' => $product->getSku(),
'price' => $product->getPrice()
];
}, $products);
}
}
For cacheable GraphQL types, implement IdentityInterface on the resolver (or a dedicated identity provider) so Magento can tag the response for full-page cache invalidation — without it, @cache(cacheable: true) has nothing to key on and the type is effectively never cached correctly.
CLI Commands
Command Class
commandName);
}
protected function configure(): void
{
$this->setDescription($this->commandDescription);
$this->addOption(
'dry-run',
'd',
InputOption::VALUE_NONE,
'Run without making changes'
);
$this->addOption(
'limit',
'l',
InputOption::VALUE_REQUIRED,
'Limit number of products',
100
);
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->title('Product Synchronization');
$limit = (int) $input->getOption('limit');
$dryRun = $input->getOption('dry-run');
if ($dryRun) {
$io->note('Running in dry-run mode');
}
try {
$externalProducts = $this->apiClient->fetchProducts($limit);
$io->progressStart(count($externalProducts));
foreach ($externalProducts as $externalProduct) {
if (!$dryRun) {
$this->syncProduct($externalProduct);
}
$io->progressAdvance();
}
$io->progressFinish();
$io->success(sprintf('Synchronized %d products', count($externalProducts)));
return Command::SUCCESS;
} catch (\Exception $e) {
$io->error('Synchronization failed: ' . $e->getMessage());
return Command::FAILURE;
}
}
}
Register Command
Vendor\Module\Console\Command\SyncProductsCommand
state->setAreaCode(Area::AREA_ADMINHTML)` (or `AREA_FRONTEND`/`AREA_GLOBAL`) — a bare CLI command defaults to no area, and area-dependent services throw a `LocalizedException` otherwise.
## Cron Jobs
### Cron Class
```php
logger->info('Running expired product cleanup');
try {
$expiredProducts = $this->findExpiredProducts();
foreach ($expiredProducts as $product) {
$product->setStatus(Status::STATUS_DISABLED);
$this->productRepository->save($product);
}
$this->logger->info(sprintf('Cleaned up %d expired products', count($expiredProducts)));
} catch (\Exception $e) {
$this->logger->error('Cleanup failed: ' . $e->getMessage());
}
}
}
Cron Configuration
0 2 * * *
*/5 * * * *
Cron Groups (for large scale)
1
4
2
10
1440
60
1
Message Queue
Not every project needs all four queue XML files — communication.xml (topic schema) is the one that's always required. Add queue_topology.xml, queue_publisher.xml, queue_consumer.xml only for what the use case actually needs (e.g. just queue_publisher.xml when publishing to a queue a third party already owns).
Publisher Configuration
Queue Consumer
handler->process($data);
}
}
Message Class
Pitfalls recap
- The
resource refinwebapi.xmlmust match an actualiddeclared inacl.xml— a typo here fails silently with a 403, not a config error. - Always throw
NoSuchEntityException(not returnnull) when a repository can't find an entity — the WebAPI framework maps it to a proper 404. - A configured consumer (
queue.xml) does nothing on its own — it must actually be running as a process via cron or a supervisor (bin/magento queue:consumers:start), or messages just pile up in thequeue_messagetables. Seemagento2-performance-audit. - GraphQL resolvers get no automatic ACL check — validate the customer/admin context explicitly inside
resolve()if the field exposes anything sensitive.
cron_schedule rows move through pending → running → success/error/missed. Never TRUNCATE cron_schedule to "fix" a stuck cron — query it (WHERE status IN ('error','missed')) to diagnose the actual cause instead, since truncating destroys the run history you'd need to find it.
Verification
# Test CLI command
bin/magento vendor:products:sync --dry-run --limit=10
# List registered commands
bin/magento list | grep vendor
# Run cron manually
bin/magento cron:run --group=custom
# Check queue consumers
bin/magento queue:consumers:list
# Start message queue consumer
bin/magento queue:consumers:start vendor.product.update.consumer
# Test REST API
curl -X GET "http://localhost/V1/vendor/product/1" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json"
# Clear API cache
bin/magento cache:clean config
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: ddtcorex
- Source: ddtcorex/dev-skills-hub
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.