--- name: create-frontend-controller description: 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. --- # 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 requests - `HttpPostActionInterface` - 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: ```php 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: - `JsonFactory` for JSON responses - `PageFactory` for full page responses - `RedirectFactory` for redirects - `RawFactory` for raw output ## Step-by-Step Implementation ### Step 1: Create routes.xml Define your route configuration in `etc/frontend/routes.xml`: ```xml ``` **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 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 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 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 Page Title ``` ### Step 5: Create Template (For Page Controllers) Create template file: `view/frontend/templates/custom/template.phtml` ```php

escapeHtml(__('Custom Page')) ?>

escapeHtml(__('Your content here')) ?>

``` ### Step 6: Clear Cache and Test ```bash # 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 ```php 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 ```php 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 ```php 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 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.xml` is 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-data` and `fetch()` calls - Use ViewModels to prepare data for Alpine.js consumption - Follow PSR-12 coding standards - Always use `ddev exec` prefix for Magento CLI commands