Claude Code Plugins

Community-maintained marketplace

Feedback

create-frontend-controller

@ProxiBlue/claude-skills
3
0

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 Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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:

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 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.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

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