Agent skill
create-frontend-controller
Creates a frontend controller action in Magento 2 for the storefront. Use when building custom frontend pages, AJAX endpoints, form submission handlers, or API-like endpoints for JavaScript.
Install this agent skill to your Project
npx add-skill https://github.com/ProxiBlue/claude-skills/tree/main/create-frontend-controller
SKILL.md
Create Frontend Controller Action
Description
This skill guides you through creating a frontend controller action in Adobe Commerce/Magento 2 (Mage-OS). Frontend controllers handle HTTP requests and return responses for the storefront area.
When to Use
- Creating custom frontend endpoints for AJAX requests
- Building custom pages or actions accessible to customers
- Implementing custom form submissions
- Creating API-like endpoints for frontend JavaScript
Prerequisites
- Existing Magento 2 module with proper structure
- Understanding of dependency injection
- Knowledge of Magento routing system
Best Practices from Adobe Documentation
1. Use HTTP Method-Specific Interfaces
Always implement HTTP method-specific action interfaces:
HttpGetActionInterface- For GET requestsHttpPostActionInterface- For POST requests- Both interfaces can be implemented for endpoints accepting multiple methods
2. Use Strict Types
Always declare strict types at the top of controller files:
declare(strict_types=1);
3. Use Dependency Injection
Never use ObjectManager directly. Always inject dependencies via constructor.
4. Return Proper Result Objects
Use result factories to return appropriate response types:
JsonFactoryfor JSON responsesPageFactoryfor full page responsesRedirectFactoryfor redirectsRawFactoryfor raw output
Step-by-Step Implementation
Step 1: Create routes.xml
Define your route configuration in etc/frontend/routes.xml:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="standard">
<route id="yourmodule" frontName="yourmodule">
<module name="Vendor_ModuleName" />
</route>
</router>
</config>
URL Structure: https://yourdomain.com/{frontName}/{controller}/{action}
Step 2: Create Controller Directory Structure
Create the controller directory:
app/code/Vendor/ModuleName/Controller/
└── ControllerName/
└── ActionName.php
Example: Controller/Custom/Search.php maps to URL: /yourmodule/custom/search
Step 3: Create Controller Action Class
Example 1: JSON Response Controller (GET/POST)
<?php
/**
* Copyright © [Year] [Your Company]
* All rights reserved.
*/
declare(strict_types=1);
namespace Vendor\ModuleName\Controller\ControllerName;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Controller\ResultInterface;
class ActionName implements HttpGetActionInterface, HttpPostActionInterface
{
/**
* @var JsonFactory
*/
private JsonFactory $resultJsonFactory;
/**
* @var RequestInterface
*/
private RequestInterface $request;
/**
* Constructor
*
* @param JsonFactory $resultJsonFactory
* @param RequestInterface $request
*/
public function __construct(
JsonFactory $resultJsonFactory,
RequestInterface $request
) {
$this->resultJsonFactory = $resultJsonFactory;
$this->request = $request;
}
/**
* Execute action
*
* @return ResultInterface
*/
public function execute(): ResultInterface
{
// Get request parameters
$searchKey = $this->request->getParam('searchKey');
$page = (int)$this->request->getParam('page', 1);
$limit = (int)$this->request->getParam('limit', 10);
// Your business logic here
$data = [
'success' => true,
'message' => 'Action completed successfully',
'data' => [
'searchKey' => $searchKey,
'page' => $page,
'limit' => $limit
]
];
// Return JSON response
$resultJson = $this->resultJsonFactory->create();
return $resultJson->setData($data);
}
}
Example 2: Page Response Controller (GET only)
<?php
/**
* Copyright © [Year] [Your Company]
* All rights reserved.
*/
declare(strict_types=1);
namespace Vendor\ModuleName\Controller\ControllerName;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\View\Result\PageFactory;
use Magento\Framework\View\Result\Page;
class ActionName implements HttpGetActionInterface
{
/**
* @var PageFactory
*/
private PageFactory $resultPageFactory;
/**
* Constructor
*
* @param PageFactory $resultPageFactory
*/
public function __construct(
PageFactory $resultPageFactory
) {
$this->resultPageFactory = $resultPageFactory;
}
/**
* Execute action
*
* @return Page
*/
public function execute(): Page
{
$resultPage = $this->resultPageFactory->create();
$resultPage->getConfig()->getTitle()->set(__('Page Title'));
return $resultPage;
}
}
Example 3: Redirect Response Controller
<?php
/**
* Copyright © [Year] [Your Company]
* All rights reserved.
*/
declare(strict_types=1);
namespace Vendor\ModuleName\Controller\ControllerName;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\Controller\Result\RedirectFactory;
use Magento\Framework\Controller\ResultInterface;
class ActionName implements HttpGetActionInterface
{
/**
* @var RedirectFactory
*/
private RedirectFactory $resultRedirectFactory;
/**
* Constructor
*
* @param RedirectFactory $resultRedirectFactory
*/
public function __construct(
RedirectFactory $resultRedirectFactory
) {
$this->resultRedirectFactory = $resultRedirectFactory;
}
/**
* Execute action
*
* @return ResultInterface
*/
public function execute(): ResultInterface
{
$resultRedirect = $this->resultRedirectFactory->create();
$resultRedirect->setPath('customer/account');
return $resultRedirect;
}
}
Step 4: Create Layout XML (For Page Controllers)
If returning a page, create layout XML: view/frontend/layout/yourmodule_controllername_actionname.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">
<head>
<title>Page Title</title>
</head>
<body>
<referenceContainer name="content">
<block class="Vendor\ModuleName\Block\CustomBlock"
name="custom.block"
template="Vendor_ModuleName::custom/template.phtml"/>
</referenceContainer>
</body>
</page>
Step 5: Create Template (For Page Controllers)
Create template file: view/frontend/templates/custom/template.phtml
<?php
/**
* @var \Magento\Framework\View\Element\Template $block
* @var \Magento\Framework\Escaper $escaper
*/
?>
<div class="custom-content">
<h1><?= $escaper->escapeHtml(__('Custom Page')) ?></h1>
<p><?= $escaper->escapeHtml(__('Your content here')) ?></p>
</div>
Step 6: Clear Cache and Test
# Clear cache
ddev exec bin/magento cache:flush
# Upgrade setup (if new module)
ddev exec bin/magento setup:upgrade
# Test the endpoint
curl https://ntotank.ddev.site/yourmodule/controllername/actionname
Common Patterns
Pattern 1: AJAX Endpoint with Collection
public function execute(): ResultInterface
{
$searchKey = $this->request->getParam('searchKey');
// Load collection
$collection = $this->collectionFactory->create();
$collection->addFieldToFilter('name', ['like' => "%{$searchKey}%"]);
$collection->setPageSize(10);
// Format results
$results = [];
foreach ($collection as $item) {
$results[] = [
'id' => $item->getId(),
'name' => $item->getName(),
'url' => $item->getUrl()
];
}
$resultJson = $this->resultJsonFactory->create();
return $resultJson->setData([
'items' => $results,
'total' => $collection->getSize()
]);
}
Pattern 2: Form Submission Handler
public function execute(): ResultInterface
{
if (!$this->request->isPost()) {
$resultRedirect = $this->resultRedirectFactory->create();
return $resultRedirect->setPath('*/*/');
}
try {
// Validate CSRF token (automatically done by Magento)
$formData = $this->request->getPostValue();
// Process form data
// ... your logic here
$this->messageManager->addSuccessMessage(__('Form submitted successfully.'));
$resultRedirect = $this->resultRedirectFactory->create();
return $resultRedirect->setPath('*/*/success');
} catch (\Exception $e) {
$this->messageManager->addErrorMessage($e->getMessage());
$resultRedirect = $this->resultRedirectFactory->create();
return $resultRedirect->setPath('*/*/');
}
}
Pattern 3: Customer Authentication Check
private \Magento\Customer\Model\Session $customerSession;
public function execute(): ResultInterface
{
if (!$this->customerSession->isLoggedIn()) {
$resultRedirect = $this->resultRedirectFactory->create();
$resultRedirect->setPath('customer/account/login');
return $resultRedirect;
}
// Continue with authenticated logic
// ...
}
Testing
Unit Test Example
Create: Test/Unit/Controller/ControllerName/ActionNameTest.php
<?php
declare(strict_types=1);
namespace Vendor\ModuleName\Test\Unit\Controller\ControllerName;
use PHPUnit\Framework\TestCase;
use Vendor\ModuleName\Controller\ControllerName\ActionName;
class ActionNameTest extends TestCase
{
public function testExecuteReturnsJsonResult(): void
{
// Setup mocks
$resultJsonFactory = $this->createMock(\Magento\Framework\Controller\Result\JsonFactory::class);
$request = $this->createMock(\Magento\Framework\App\RequestInterface::class);
// Create controller instance
$controller = new ActionName($resultJsonFactory, $request);
// Execute and assert
$result = $controller->execute();
$this->assertInstanceOf(\Magento\Framework\Controller\ResultInterface::class, $result);
}
}
Troubleshooting
Issue: 404 Not Found
- Check
routes.xmlis in correct location (etc/frontend/routes.xml) - Verify frontName is unique
- Run
ddev exec bin/magento setup:upgrade - Clear cache:
ddev exec bin/magento cache:flush
Issue: Controller Not Loading
- Check class namespace matches directory structure
- Ensure controller implements HttpGetActionInterface or HttpPostActionInterface
- Check file naming: Controller file must match class name exactly
Issue: JSON Response Not Working
- Ensure you're returning JsonFactory result
- Check Content-Type header is set correctly
- Verify no output before returning result
References
- Adobe Commerce Frontend Core Documentation: https://github.com/adobedocs/commerce-frontend-core
- Magento 2 Routing: https://developer.adobe.com/commerce/php/architecture/modules/routing/
- Controller Action Interfaces: Use HttpGetActionInterface and HttpPostActionInterface for modern Magento 2
NTOTanks-Specific Notes
- For AJAX endpoints, follow the pattern used in
ProxiBlue_SearchDynaTable - Integrate with Alpine.js on frontend using
x-dataandfetch()calls - Use ViewModels to prepare data for Alpine.js consumption
- Follow PSR-12 coding standards
- Always use
ddev execprefix for Magento CLI commands
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
create-backend-controller
Creates a backend (adminhtml) controller action in Magento 2 with proper ACL, routing, authorization, and admin UI integration. Use when building admin pages, AJAX endpoints, form handlers, or mass actions.
agent-teams
page-banner-setup
hyva-module-compatibility
Identify and fix Magento 2 module compatibility issues with Hyvä Themes. Covers block plugin bypasses, RequireJS/Knockout replacements, ViewModels, and Alpine.js integration for modules that work in admin but fail on Hyvä frontend.
magento-diagnostic
Comprehensive Magento 2 system diagnostic skill that gathers cache status, index status, module information, configuration, logs, and performance metrics for rapid troubleshooting.
github-analysis
Systematically analyze GitHub tickets with proper local reproduction. Automatically substitutes production URLs with local DDEV hosts, ensures database sync, downloads missing assets, and reproduces issues locally before proposing solutions.
Didn't find tool you were looking for?