---
title: "Create a backend module with Core functionality"
manual: "TYPO3 Explained"
version: "main"
permalink: "https://docs.typo3.org/permalink/t3coreapi:backend-modules-template-without-extbase@main"
source: "ExtensionArchitecture/HowTo/BackendModule/CreateModule.rst"
rendered: "2026-09-18T15:39:49+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@main).

> [!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@main).
> If you are building  a simple backend module, it makes sense to work without Extbase.

## Basic controller {#backend-modules-template-without-extbase-basic-controller}

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

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

```php
<?php

use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Backend\Template\Components\ComponentFactory;
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,
    private ComponentFactory $componentFactory,
    // ...
  ) {}
}

```

> [!NOTE]
> A backend controller should be tagged with the
> `\TYPO3\CMS\Backend\Attribute\AsController` (`#[AsController]`) attribute.

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 {#backend-modules-template-without-extbase-main-entry}

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
<?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('examples.admin_module.mod:debug'),
        ),
        'password' => htmlspecialchars(
          $languageService->sL('examples.admin_module.mod:password'),
        ),
        'index' => htmlspecialchars(
          $languageService->sL('examples.admin_module.mod:index'),
        ),
      ],
    ];

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

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

    $title = $languageService->sL('examples.admin_module.mod:mlang_tabs_tab');
    switch ($moduleData->get('function')) {
      case 'debug':
        $moduleTemplate->setTitle(
          $title,
          $languageService->sL('examples.admin_module.mod:module.menu.debug'),
        );
        return $this->debugAction($request, $moduleTemplate);
      case 'password':
        $moduleTemplate->setTitle(
          $title,
          $languageService->sL('examples.admin_module.mod:module.menu.password'),
        );
        return $this->passwordAction($moduleTemplate);
      default:
        $moduleTemplate->setTitle(
          $title,
          $languageService->sL('examples.admin_module.mod:module.menu.log'),
        );
        return $this->indexAction($request, $moduleTemplate);
    }
  }
}

```

## Actions {#backend-modules-template-without-extbase-actions}

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

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

```php
<?php

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Backend\Template\ModuleTemplate;

final readonly class AdminModuleController
{
  private 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
<?php

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(
    ModuleTemplate $view,
  ): void {
    $buttonBar = $view->getDocHeaderComponent()->getButtonBar();
    $uriBuilderPath = $this->uriBuilder->buildUriFromRoute('web_list', ['id' => 0]);
    $list = $this->componentFactory->createLinkButton()
        ->setHref($uriBuilderPath)
        ->setTitle('A Title')
        ->setShowLabelText(true)
        ->setIcon($this->iconFactory->getIcon('actions-extension-import', IconSize::SMALL));
    $buttonBar->addButton($list, ButtonBar::BUTTON_POSITION_LEFT, 1);
  }
}

```

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

## Template example {#backend-modules-template-without-extbase-template-example}

**EXT:examples/Resources/Private/Templates/AdminModule/Debug.fluid.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:examples.messages:function_debug"/></h1>
    <p><f:translate key="LLL:examples.messages: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>`.
