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.
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 ommitted 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_
.
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/MyFrontendModuleController.php
namespace App\Controller\FrontendModule;
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
use Contao\CoreBundle\Exception\RedirectResponseException;
use Contao\CoreBundle\ServiceAnnotation\FrontendModule;
use Contao\ModuleModel;
use Contao\PageModel;
use Contao\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* @FrontendModule(category="miscellaneous")
*/
class MyFrontendModuleController 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 annotations. 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.
The contao.frontend_module
tag can be configured further more. The following
options are available.
Option | Type | Description |
---|---|---|
name | string |
Must be contao.frontend_module . |
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. |
type | string |
Optional: The type mentioned in Type can be customized. |
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. |
A more complex example of a front end module could look like this.
# config/services.yaml
services:
App\Controller\FrontendModule\MyFrontendModuleController:
tags:
-
name: contao.frontend_module
category: texts
method: getCustomResponse
template: mod_my_frontend_module
renderer: esi
type: my_custom_type
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.
This feature is available in Contao 4.8 and later.
Instead of tagging the front end module controller service via the service configuration,
the service tag can also be configured through annotations, as already used in the
code example above. The annotation can be used on the class of the content element,
if the class is invokable (has an __invoke
method) or extends from the AbstractFragmentController
.
Otherwise the annotation can be used on the method that will deliver the response.
The following example uses the annotation and only defines the category under which the module should be displayed within the type select of the back end, as this is the only required property that needs to be set.
// src/Controller/FrontendModule/ExampleController.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 ExampleController extends AbstractFrontendModuleController
{
protected function getResponse(Template $template, ModuleModel $model, Request $request): Response
{
return $template->getResponse();
}
}
Every other property is inferred from the class name or uses the default. The module
type in this case will be example
, the template will be mod_example
and the
renderer will be forward
.
The following example sets the type of the module to my_example
, puts it in the
miscellaneous
category, sets the template name to mod_some_example
and defines
the renderer to be forward
(which is the default):
/**
* @FrontendModule("my_example",
* category="miscellaneous",
* template="mod_some_example",
* renderer="forward"
* )
*/
class ExampleController extends AbstractFrontendModuleController
{
}
You can also use class constants within annotations. This can be helpful to make the module’s type a reusable reference:
/**
* @FrontendModule(ExampleController::TYPE, category="miscellaneous")
*/
class ExampleController extends AbstractFrontendModuleController
{
public const TYPE = 'my_module';
}
// contao/dca/tl_module.php
use App\Controller\FrontendModule\ExampleController;
$GLOBALS['TL_DCA']['tl_module']['palettes'][ExampleController::TYPE] =
'{title_legend},name,type;{redirect_legend},jumpTo'
;
// contao/languages/en/modules.php
use App\Controller\FrontendModule\ExampleController;
$GLOBALS['TL_LANG']['FMD'][ExampleController::TYPE] = [
'My example module',
'A front end module for testing purposes.',
];
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/MyFrontendModuleController.php
namespace App\Controller\FrontendModule;
use Contao\CoreBundle\Controller\FrontendModule\AbstractFrontendModuleController;
use Contao\CoreBundle\Exception\RedirectResponseException;
use Contao\CoreBundle\ServiceAnnotation\FrontendModule;
use Contao\ModuleModel;
use Contao\PageModel;
use Contao\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* @FrontendModule(category="miscellaneous")
*/
class MyFrontendModuleController 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();
}
}