Create a backend module with Core functionality

This page covers the backend template view, using only Core functionality without Extbase. See also the Backend module API.

Basic controller

When creating a controller without Extbase an instance of ModuleTemplate is required to return the rendered template:

Class T3docs\Examples\Controller\AdminModuleController
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
use TYPO3\CMS\Core\Imaging\IconFactory;

final readonly class AdminModuleController
{
    public function __construct(
        private ModuleTemplateFactory $moduleTemplateFactory,
        private IconFactory $iconFactory,
        private UriBuilder $uriBuilder,
        // ...
    ) {}
}
Copied!

If the controller is not tagged with the \TYPO3\CMS\Backend\Attribute\AsController attribute, it must be registered in Configuration/Services.yaml with the backend.controller tag for dependency injection to work:

EXT:examples/Configuration/Services.yaml
services:
  _defaults:
    autowire: true
    autoconfigure: true
    public: false

  T3docs\Examples\:
    resource: '../Classes/*'
    exclude: '../Classes/Domain/Model/*'

  T3docs\Examples\Controller\AdminModuleController:
    tags: ['backend.controller']
Copied!

Main entry point

The handleRequest() method is the main entry point which triggers only the allowed actions. This makes it possible to include e.g. Javascript for all actions in the controller.

Class T3docs\Examples\Controller\AdminModuleController
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

final readonly class AdminModuleController
{
    public function handleRequest(ServerRequestInterface $request): ResponseInterface
    {
        $languageService = $this->getLanguageService();

        $allowedOptions = [
            'function' => [
                'debug' => htmlspecialchars(
                    $languageService->sL('LLL:EXT:examples/Resources/Private/Language/AdminModule/locallang.xlf:debug'),
                ),
                'password' => htmlspecialchars(
                    $languageService->sL('LLL:EXT:examples/Resources/Private/Language/AdminModule/locallang.xlf:password'),
                ),
                'index' => htmlspecialchars(
                    $languageService->sL('LLL:EXT:examples/Resources/Private/Language/AdminModule/locallang.xlf:index'),
                ),
            ],
        ];

        $moduleData = $request->getAttribute('moduleData');
        if ($moduleData->cleanUp($allowedOptions)) {
            $this->getBackendUser()->pushModuleData($moduleData->getModuleIdentifier(), $moduleData->toArray());
        }

        $moduleTemplate = $this->moduleTemplateFactory->create($request);
        $this->setUpDocHeader($request, $moduleTemplate);

        $title = $languageService->sL('LLL:EXT:examples/Resources/Private/Language/AdminModule/locallang_mod.xlf:mlang_tabs_tab');
        switch ($moduleData->get('function')) {
            case 'debug':
                $moduleTemplate->setTitle(
                    $title,
                    $languageService->sL('LLL:EXT:examples/Resources/Private/Language/AdminModule/locallang.xlf:module.menu.debug'),
                );
                return $this->debugAction($request, $moduleTemplate);
            case 'password':
                $moduleTemplate->setTitle(
                    $title,
                    $languageService->sL('LLL:EXT:examples/Resources/Private/Language/AdminModule/locallang.xlf:module.menu.password'),
                );
                return $this->passwordAction($request, $moduleTemplate);
            default:
                $moduleTemplate->setTitle(
                    $title,
                    $languageService->sL('LLL:EXT:examples/Resources/Private/Language/AdminModule/locallang.xlf:module.menu.log'),
                );
                return $this->indexAction($request, $moduleTemplate);
        }
    }
}
Copied!

Actions

Now create an example debugAction() and assign variables to your view as you would normally do.

Class T3docs\Examples\Controller\AdminModuleController
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Template\ModuleTemplate;

final readonly class AdminModuleController
{
    protected function debugAction(
        ServerRequestInterface $request,
        ModuleTemplate $view,
    ): ResponseInterface {
        $body = $request->getParsedBody();
        if (is_array($body)) {
            $cmd = $body['tx_examples_admin_examples']['cmd'] ?? 'cookies';
            switch ($cmd) {
                case 'cookies':
                    $this->debugCookies();
                    break;
                default:
                    // do something else
            }

            $view->assignMultiple(
                [
                    'cookies' => $request->getCookieParams(),
                    'lastcommand' => $cmd,
                ],
            );
        }
        return $view->renderResponse('AdminModule/Debug');
    }
}
Copied!

The DocHeader

To add a DocHeader button use $view->getDocHeaderComponent()->getButtonBar() and makeLinkButton() to create the button. Finally, use addButton() to add it.

Class T3docs\Examples\Controller\AdminModuleController
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
use TYPO3\CMS\Backend\Template\ModuleTemplate;
use TYPO3\CMS\Core\Imaging\IconSize;

final readonly class AdminModuleController
{
    private function setUpDocHeader(
        ServerRequestInterface $request,
        ModuleTemplate $view,
    ): void {
        $buttonBar = $view->getDocHeaderComponent()->getButtonBar();
        $uriBuilderPath = $this->uriBuilder->buildUriFromRoute('web_list', ['id' => 0]);
        $list = $buttonBar->makeLinkButton()
            ->setHref($uriBuilderPath)
            ->setTitle('A Title')
            ->setShowLabelText(true)
            ->setIcon($this->iconFactory->getIcon('actions-extension-import', IconSize::SMALL->value));
        $buttonBar->addButton($list, ButtonBar::BUTTON_POSITION_LEFT, 1);
    }
}
Copied!

Template example

EXT:examples/Resources/Private/Templates/AdminModule/Debug.html
<html data-namespace-typo3-fluid="true" xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers">

   <f:layout name="Module" />

   <f:section name="Content">
      <h1><f:translate key="LLL:EXT:examples/Resources/Private/Language/locallang_examples.xlf:function_debug"/></h1>
      <p><f:translate key="LLL:EXT:examples/Resources/Private/Language/locallang_examples.xlf:function_debug_intro"/></p>
      <p><f:debug inline="1">{cookies}</f:debug></p>
   </f:section>
</html>
Copied!