Quick reference handbook

since 5.0 Here are some examples to quickly get you started. We only target Contao 5 in the examples, but with some tweaking you might be able to apply them to Contao 4.13 as well.

How to…

This list is far from being complete — if you are missing a typical scenario, please contribute it!

Examples

Fragment controller

We create a content element controller in this example. If you want to create a module, these work very similar: you then need to useAbstractFrontendModuleController as the abstract base class, [#AsFrontendModule] as the attribute and frontend_module/_base.html.twig as the parent template, instead. If you want to make DCA adjustments, also target tl_module instead of tl_content.

Example

Create a src/Controller/FruitSaladController.php controller that renders a template with some fruits as parameters:

<?php
declare(strict_types=1);

namespace App\Controller;

use Contao\ContentModel;
use Contao\CoreBundle\Controller\ContentElement\AbstractContentElementController;
use Contao\CoreBundle\DependencyInjection\Attribute\AsContentElement;
use Contao\CoreBundle\Twig\FragmentTemplate;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

#[AsContentElement(category: 'food')]
class FruitSaladController extends AbstractContentElementController
{
    protected function getResponse(FragmentTemplate $template, ContentModel $model, Request $request): Response
    {
        $template->set('fruits', [
            'acai', 'blackberry', 'currant', 'durian', 'elderberry', 'fig', 'grape'
        ]);
        
        return $template->getResponse();
    }
}

Create or adjust the contao/dca/tl_content.php file, so that there is something to see in the back end when editing a fruit_salad element:

// Define the palette for the back end edit mask; we are only using a minimal set for now
$GLOBALS['TL_DCA']['tl_content']['palettes']['fruit_salad'] = 
    '{type_legend},type,headline;' .    // add the type selector and headline field 
    '{template_legend:hide},customTpl;' // allow selecting template variants    
;

Create a content_element/fruit_salad.html.twig template:

{% extends "@Contao/content_element/_base.html.twig" %}

{% block content %}
    <p>We put fruit like {{ fruits|join(', ') }} in our tropical salads.</p>
{% endblock %}

Create a content_element/fruit_salad/random.html.twig variant template — here we want to output the fruits as a randomized list instead of a block of text:

{% extends "@Contao/content_element/_base.html.twig" %}
{% use "@Contao/component/_list.html.twig" %}

{# For this, we use the "list" component, that already brings the functionality
   to randomize the order (client-side)… #}
{% block content %}
    <b>Why we like our salads? It's all about the ingredients:</b>
    {% with {items: fruits, randomize_order: true} %}
        {{ block('list_component') }}
    {% endwith %}
{% endblock %}

{# … and overwrite the "list_item" block to output our fruit inside the <li> tag: #}
{% block list_item %}     
    <span class="fruit--{{item}}">{{ item|capitalize }}</span> is an amazing fruit. 
{% endblock %}

Dynamically render templates

Sometimes you might want to dynamically decide which template you want to render from inside your fragment controller. There are two ways of doing it, by using the given FragmentTemplate or by calling render() yourself. For brevity, we only focus on the implementation of the getReponse() method.

Example

Setting the template name

// …

protected function getResponse(FragmentTemplate $template, ContentModel $model, Request $request): Response
{
    // Work with the FragmentTemplate like usual but also overwrite the
    // inferred template name
    $template->set('foo', 'bar')
    $template->setName('@Contao/content_element/custom/thing.html.twig');
    
    return $template->getResponse();       
}
Example

Calling render() yourself

// …

protected function getResponse(FragmentTemplate $template, ContentModel $model, Request $request): Response
{
    $parameters = [
        'foo' => 'bar',
        // Merge in the pre-built data like type, headline or CSS classes
        // information when compiling the template parameters
        ...$template->getData()
    ];

    return $this->render('@Contao/content_element/amazing_fruit.html.twig', $parameters);        
}

When calling $template->getResponse(), internally, $this->render(null, $template->getData()) will get executed — by using null as the first argument, our abstract controller class will use the inferred default template name. This makes both variants effectively equivalent.

Adjust the base template

We want to add a custom data attribute that contains the element’s ID (e.g. data-element="42") to the div, that wraps each content element. Overwriting the content_element/_base.html.twig is the way to go. For this example, we don’t even have to adjust a block. The way HtmlAttributes are set up, it is enough to extend from the original template and define our own attribute.

Example
{% extends "@Contao/content_element/_base.html.twig" %}

{% set attributes = attrs(attributes|default).set('data-element', data.id) %}

If you would need more data, that was not set by the abstract base class, you could create your own filter or function and then either use the element’s ID as an argument or make the filter/function context-aware, to get the information to your template.