Creating the Layout XML and Controller for the New Page
Creating the Layout XML and Controller for the New Page
~8 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026
In this chapter, we get the team page running for the very first time - still without real content, but reachable at the target URL /team. That takes four files: module registration, route, controller, and an (empty) Layout XML.
Module scaffolding
<?php
declare(strict_types=1);
use Magento\Framework\Component\ComponentRegistrar;
ComponentRegistrar::register(
ComponentRegistrar::MODULE,
'Mironsoft_TeamPage',
__DIR__
);<?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="Mironsoft_TeamPage" setup_version="1.0.0">
<sequence>
<module name="Magento_Theme"/>
</sequence>
</module>
</config>The route: routes.xml
etc/frontend/routes.xml registers the front name team for the frontend and ties it to our module:
<?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="team" frontName="team">
<module name="Mironsoft_TeamPage"/>
</route>
</router>
</config>The controller: Controller/Index/Index.php
The folder name Index under Controller/ matches the URL segment after the front name, and the class name Index matches the action - together that gives /team/index/index, which Magento automatically shortens to /team (default controller/default action). The controller uses constructor property promotion and just serves the page - data will come later via the ViewModel, not through the controller itself:
<?php
declare(strict_types=1);
namespace Mironsoft\TeamPage\Controller\Index;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\Result\Page;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Controller\ResultInterface;
/**
* Serves the team overview page at /team.
*/
class Index implements HttpGetActionInterface
{
/**
* @param ResultFactory $resultFactory Creates the page result for the team page.
*/
public function __construct(
private readonly ResultFactory $resultFactory,
) {
}
/**
* Renders the team page via the team_index_index layout handle.
*
* @return ResultInterface|ResponseInterface
*/
public function execute(): ResultInterface|ResponseInterface
{
/** @var Page $resultPage */
$resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE);
$resultPage->getConfig()->getTitle()->set('Our Team');
return $resultPage;
}
}HttpGetActionInterface, instead of the older, more general Action base class, makes it explicit that this controller only serves GET requests - the right choice for a pure display page with no form submission.
A first, empty layout handle
So the page returns a valid response at all (instead of an empty default layout), we already create the layout handle now - for the moment with a simple placeholder block that we'll replace with the real template in chapter 20:
<?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">
<body>
<referenceContainer name="content">
<block class="Magento\Framework\View\Element\Template"
name="team.page"
template="Mironsoft_TeamPage::team/index.phtml"/>
</referenceContainer>
</body>
</page>Activating and testing the module
bin/magento module:enable Mironsoft_TeamPage
bin/magento setup:upgrade
bin/cache-cleanAchtung: At this point, the template team/index.phtml doesn't exist yet (that comes in chapter 20) - so visiting /team right now would throw a "Template file ... does not exist" error. That's expected at this stage; chapters 19 and 20 close that gap.