---
title: "Create a backend module with Core functionality"
manual: "TYPO3 Explained"
version: "12.4"
permalink: "https://docs.typo3.org/permalink/t3coreapi:backend-modules-template-without-extbase@12.4"
source: "ExtensionArchitecture/HowTo/BackendModule/CreateModule.rst"
rendered: "2026-10-21T15:29:37+00:00"
---

# Create a backend module with Core functionality {#backend-modules-template-without-extbase}

This page covers the backend template view, using only Core functionality
without Extbase. See also the [Backend module API](https://docs.typo3.org/permalink/t3coreapi:backend-modules@12.4).

> [!TIP]
> If you want to do extensive data modeling, you may want to
> use [Extbase templating](https://docs.typo3.org/permalink/t3coreapi:backend-modules-template@12.4).
> If you are building  a simple backend module, it makes sense to work without Extbase.

## Basic controller {#backend-modules-template-without-extbase-manual-tagging}

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

**Class T3docs\\Examples\\Controller\\AdminModuleController**

```php
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(
        protected ModuleTemplateFactory $moduleTemplateFactory,
        protected IconFactory $iconFactory,
        private UriBuilder $uriBuilder,
        // ...
    ) {}
}
```

<!-- TODO: no Markdown rendering for "versionadded" -->

A backend controller can be tagged with the
\TYPO3\CMS\Backend\Attribute\AsController attribute. This way, the
registration of the controller
in the Configuration/Services.yaml file is no longer necessary.Until TYPO3 v12.4.8 the attribute was named
\TYPO3\CMS\Backend\Attribute\Controller and has been renamed to
AsController with TYPO3 v12.4.9. Both work with TYPO3 v12 and v13,
but developers should use #[AsController] for upwards compatibility,
since #[Controller] has been deprecated with TYPO3 v13.

If the controller is not tagged with the `\TYPO3\CMS\Backend\Attribute\AsController`
attribute, it must be registered in [`Configuration/Services.yaml`](../../FileStructure/Configuration/ServicesYaml.md#file-extension-configuration-services-yaml)
with the `backend.controller` tag for dependency injection to work:

**EXT:examples/Configuration/Services.yaml**

```yaml
services:
  _defaults:
    autowire: true
    autoconfigure: true
    public: false

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

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

## Main entry point {#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**

```php
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);
        }
    }
}
```

## Actions {#actions}

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

**Class T3docs\\Examples\\Controller\\AdminModuleController**

```php
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');
    }
}
```

## The DocHeader {#backend-modules-template-without-extbase-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**

```php
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
use TYPO3\CMS\Backend\Template\ModuleTemplate;
use TYPO3\CMS\Core\Imaging\Icon;

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', Icon::SIZE_SMALL));
        $buttonBar->addButton($list, ButtonBar::BUTTON_POSITION_LEFT, 1);
    }
}
```

> [!NOTE]
> **See also**
>
> [Button components](https://docs.typo3.org/permalink/t3coreapi:button-components@12.4)

## Template example {#template-example}

**EXT:examples/Resources/Private/Templates/AdminModule/Debug.html**

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

> [!NOTE]
> Some Fluid tags do not work in non-Extbase context such as
>
> `<f:form>`.
