Front End Modules

This covers the documentation on how to create front end modules in Contao 4.6 and up. In previous Contao version, front end modules must extend from \Contao\Module and then be registered via the $GLOBALS['FE_MOD'] array.

Front end modules in Contao are used for more complex functionality, which are typically used on more than one page or even in page layouts. They are used to generate dynamic content, like news lists, displaying the detailed content of news or navigation items.

These modules are implemented as so called fragment controllers which Contao then renders into the main content, using their defined renderer. See the caching documentation for more information.

Creating a front end module is very similar to creating content elements.

Definition

To create a new front end module, the following things must be defined and implemented:

  • Fragment Controller
    The actual implementation of the front end module is done via a class that extends from AbstractFrontendModuleController of the Contao core.

  • Service Tag
    To identify the controller as a Contao front end module, the service must be tagged with service tag contao.frontend_module.

    • Type
      The type of a front end module is a specific string which is used to identify the front end module’s template and DCA palette. The type can be set in the service tag. If omitted the type will be automatically generated by converting the class name of the controller from pascal case to snake case and removing a possible Controller postfix.

    • Category
      All front end modules are categorised within the type dropdown of the front end module’s palette. A category must be defined in the service tag for each module.

  • Template
    The template name follows the naming convention mentioned beforehand. It prepends the type of the module with the prefix mod_.

Example

Usually a front end module is based on a specific palette in the tl_module DCA configuration.

// contao/dca/tl_module.php
$GLOBALS['TL_DCA']['tl_module']['palettes']['my_frontend_module'] = 
    '{title_legend},name,type;{redirect_legend},jumpTo'
;

This very simple palette enables a back end user to select a redirect page using the pre-existing field jumpTo via the create and edit view of this front end module.

The controller for this module could look like this:

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

use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
use Contao\CoreBundle\Exception\RedirectResponseException;
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
use Contao\ModuleModel;
use Contao\PageModel;
use Contao\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

#[AsFrontendModule(category: 'miscellaneous')]
class ExampleModuleController extends AbstractFrontendModuleController
{
    protected function getResponse(Template $template, ModuleModel $model, Request $request): Response
    {
        if ($request->isMethod('post')) {
            if (null !== ($redirectPage = PageModel::findByPk($model->jumpTo))) {
                throw new RedirectResponseException($redirectPage->getAbsoluteUrl());
            }
        }

        $template->action = $request->getUri();

        return $template->getResponse();
    }
}

In this example the service tag was implemented via PHP attributes. The controller itself processes the request and checks, if it was a POST request. In that case, the redirect page is loaded via Contao’s model functionality and a RedirectResponseException is thrown to redirect to that page.

Using the naming convention for templates mentioned above, the final template name for this front end module will be mod_my_frontend_module:

<!-- templates/mod_my_frontend_module.html5 -->
<div class="my-frontend-module">   
  <form action="<?= $this->action ?>" method="POST"> 
    <input type="hidden" name="REQUEST_TOKEN" value="{{request_token}}">
    <button type="submit">Submit</button>
  </form>
</div>

A template instance of this template will automatically be generated and passed to the controller’s main method. The controller returns the parsed template as a response.

Registration

As mentioned previously a front end module is registered by registering a controller as a service and tagging it with the contao.frontend_module service tag. The service tag supports the following options:

Option Type Description
name string Must be contao.frontend_module.
type string Optional: The type mentioned in Type can be customized.
category string Defines in which option group this front end module will be placed in the module type selector.
template string Optional: Override the generated template name.
renderer string Optional: The renderer can be changed to inline or esi. Defaults to forward. See Caching Fragments for more details.
method string Optional: Which method should be invoked on the controller.

Applying the service tag can either be done via PHP attributes, annotations or via the YAML configuration.

since 4.13 A front end module can be registered using the AsFrontendModule PHP attribute.

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

use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
use Contao\ModuleModel;
use Contao\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

#[AsFrontendModule(category:'miscellaneous')]
class ExampleModuleController extends AbstractFrontendModuleController
{
    protected function getResponse(Template $template, ModuleModel $model, Request $request): Response
    {
        return $template->getResponse();
    }
}

The above example only defines the mandatory category attribute. If you wish you can also define the other options of the service tag:

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

use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
use Contao\ModuleModel;
use Contao\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

#[AsFrontendModule('example', category:'miscellaneous', template:'mod_example', renderer:'forward', method:'__invoke')]
class ExampleModuleController extends AbstractFrontendModuleController
{
    protected function getResponse(Template $template, ModuleModel $model, Request $request): Response
    {
        return $template->getResponse();
    }
}

since 4.8 A front end module can be registered using the FrontendModule annotation. The annotation can be used on the class of the front end module, if the class is invokable (has an __invoke method) or extends from the AbstractFrontendModuleController. Otherwise the annotation can be used on the method that will deliver the response.

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

use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
use Contao\CoreBundle\ServiceAnnotation\FrontendModule;
use Contao\ModuleModel;
use Contao\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
 * @FrontendModule(category="miscellaneous")
 */
class ExampleModuleController extends AbstractFrontendModuleController
{
    protected function getResponse(Template $template, ModuleModel $model, Request $request): Response
    {
        return $template->getResponse();
    }
}

The above example only defines the mandatory category attribute. If you wish you can also define the other options of the service tag:

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

use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
use Contao\CoreBundle\ServiceAnnotation\FrontendModule;
use Contao\ModuleModel;
use Contao\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
 * @FrontendModule("example", category="miscellaneous", template="mod_example", renderer="forward", method="__invoke")
 */
class ExampleModuleController extends AbstractFrontendModuleController
{
    protected function getResponse(Template $template, ModuleModel $model, Request $request): Response
    {
        return $template->getResponse();
    }
}

since 4.8 A front end module can be registered using the contao.frontend_module service tag.

# config/services.yaml
services:
    App\Controller\FrontendModule\ExampleModuleController:
        tags:
            -
                name: contao.frontend_module
                category: miscellaneous
// src/Controller/FrontendModule/ExampleModuleController.php
namespace App\Controller\FrontendModule;

use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
use Contao\ModuleModel;
use Contao\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class ExampleModuleController extends AbstractFrontendModuleController
{
    protected function getResponse(Template $template, ModuleModel $model, Request $request): Response
    {
        return $template->getResponse();
    }
}

The above example only defines the mandatory category attribute. If you wish you can also define the other options of the service tag:

# config/services.yaml
services:
    App\Controller\FrontendModule\ExampleModuleController:
        tags:
            -
                name: contao.frontend_module
                category: miscellaneous
                template: mod_example
                renderer: forward
                method: __invoke

You can also use class constants within attributes and annotations. This can be helpful to make the module’s type a reusable reference:

#[AsFrontendModule(ExampleModuleController::TYPE, 'miscellaneous')]
class ExampleModuleController extends AbstractFrontendModuleController
{
    public const TYPE = 'my_module';
}
// contao/dca/tl_module.php
use App\Controller\FrontendModule\ExampleModuleController;

$GLOBALS['TL_DCA']['tl_module']['palettes'][ExampleModuleController::TYPE] = 
    '{title_legend},name,type;{redirect_legend},jumpTo'
;
// contao/languages/en/modules.php
use App\Controller\FrontendModule\ExampleModuleController;

$GLOBALS['TL_LANG']['FMD'][ExampleModuleController::TYPE] = [
    'My example module', 
    'A front end module for testing purposes.',
];

Translations

In order to have a nice label in the back end, we also need to add a translation for our front end module - otherwise it will only be named my_frontend_module. The translation needs to be set as follows:

// contao/languages/en/modules.php
$GLOBALS['TL_LANG']['FMD']['my_frontend_module'] = [
    'My front end module', 
    'A front end module for testing purposes.',
];

If you used a custom category for your front end module, its label can also be translated there.

Page Model

This feature is available in Contao 4.9.10 and later.

If your fragment extends from AbstractFrontendModuleController (or just AbstractFragmentController) you can use $this->getPageModel() in order to receive the \Contao\PageModel object of the currently rendered page of Contao’s site structure.

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

use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
use Contao\CoreBundle\Exception\RedirectResponseException;
use Contao\CoreBundle\DependencyInjection\Attribute\AsFrontendModule;
use Contao\ModuleModel;
use Contao\PageModel;
use Contao\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

#[AsFrontendModule(category: 'miscellaneous')]
class ExampleModuleController extends AbstractFrontendModuleController
{
    protected function getResponse(Template $template, ModuleModel $model, Request $request): Response
    {
        $page = $this->getPageModel();

        // Get some information about the current page
        $template->rootTitle = $page->rootPageTitle ?: $page->rootTitle;

        return $template->getResponse();
    }
}

Read More