Fragment Controllers


A regular page in Contao consists of various partials that build the actual page content:

  1. The page has a layout assigned, which renders the base template (usually fe_page.html5)
  2. Each page layout has multiple layout sections (header, left, main, custom ones etc.)
  3. Each layout section contain one or multiple modules or articles
  4. Each article is a container filled with content elements

Splitting the page into these partials is the foundation of flexible page building in Contao.

The concept of modules and elements exists since the beginning of Contao. Front end modules and content elements as Symfony controller fragments are available since Contao 4.5.

Before you learn about fragments, make sure to understand Symfony controllers.


In Symfony, an HTML template is usually generated by a controller. From the Symfony documentation:

A controller is a PHP function you create that reads information from the Request object and creates and returns a Response object. The response could be an HTML page, JSON, XML, a file download, a redirect, a 404 error or anything else. The controller executes whatever arbitrary logic your application needs to render the content of a page.

A normal controller has a route – an URL on your site – that will generate the response. As fragments are partials of an existing page; they do not have a route, but same as a normal controller they will get a request and generate a response. This concept is called Sub Requests in Symfony, each fragment in Contao is a sub request in Symfony.

From an HTTP perspective, each (sub) request is totally independent. Each one will get request parameters (like GET and POST data), and they can return HTTP headers (like Content Type or Cache-Control).

Fragment Controllers

Contao provides two different types of fragments out-of-the-box: Front End Modules and Content Elements. For each of these, there are abstract classes (namely AbstractFrontendModuleController and AbstractContentElementController) that mimick the behavior of a Contao module/element. They prepare the Contao template based on the request and current model, and allow to return a response with the fragment result.

Controllers in the Contao framework

Front end modules and content elements are still generated by the Contao framework and registered in the $GLOBALS['FE_MOD'] and $GLOBALS['TL_CTE'] respectively. The ModuleProxy and ContentProxy classes are responsible for making a fragment controller behave like a Contao module/element.

Extending an existing class

Modules are used to simplify, everything mentioned here works exactly the same for content elements.

Sometimes, one might need to override/extend the functionality of an existing Contao module. While you can still create a module the legacy way, it limits functionality and is not recommended anymore. However, there’s nothing limiting us from extending an existing class and using it as a controller in Contao 4!

// src/Controller/FrontendModule/AppExampleController.php
namespace App\Controller\FrontendModule;

use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
use Contao\ModuleModel;
use Contao\ModuleNewsList;
use Symfony\Component\HttpFoundation\Response;

#[AsFrontendModule(category: 'news')]
class AppExampleController extends ModuleNewsList
    public function __construct() {}

    public function __invoke(ModuleModel $model, string $section): Response
        parent::__construct($model, $section);

        return new Response($this->generate());
    // Do whatever you want here, e.g. override parent methods
Code example to extend a Contao content element

Since we are extending classes from the legacy Contao framework here, these controllers will not be automatically registered as a service by Contao. Therefore you will need to specifically register these controllers as services in your own config/services.yaml. See this article for more information.

Adding custom fragment types

The Contao fragment registry is able to render any fragment, not just frontend modules and content elements. As an example, a shop extension could support fragments for different product types, allowing third-party extension to add new product types to the shop system. To fully understand the concept, you might need strong Symfony knowledge and to reverse-engineer the Contao core. Make sure to have a look at the FragmentRegistry and RegisterFragmentsPass.

Read more