Callbacks are entry-points for custom code in the DCA. Using callbacks you can modify the static Data Container Array during runtime.
Callback functions are based on the event dispatcher pattern. They are similar to Hooks, but always bound to a specific DCA table. You can register one or more callbacks for a certain event and when the event is triggered, the callback functions are being executed. See the framework article on how to register callbacks.
You can also use anonymous functions for DCA callbacks.
The following is a reference of all available callbacks, using their service tag callback property name.
Generally these callbacks are exectued in the back end, e.g. when editing data records.
However in some instances they might also be executed by front end modules, most
prominently the member modules. In this case the parameters passed to the callback
will be different, as there will be no \Contao\DataContainer
instance for example,
which only exists in the back end. The reference below will list these differences
of the respective callbacks. Keep in mind that any extension might also execute
any of these callbacks in the front end.
config.onload
Executed when the DataContainer object is initialized. Allows you to e.g. check permissions or to modify the Data Container Array dynamically at runtime.
\Contao\DataContainer
Data Container object or null
return: void
No parameters.
return: void
This example changes the mandatory
attribute for the tl_content.text
field for a specific content element.
// src/EventListener/DataContainer/MakeTextNotMandatoryCallback.php
namespace App\EventListener\DataContainer;
use Contao\ContentModel;
use Contao\CoreBundle\ServiceAnnotation\Callback;
use Contao\DataContainer;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* @Callback(table="tl_content", target="config.onload")
*/
class MakeTextNotMandatoryCallback
{
private $requestStack;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public function __invoke(DataContainer $dc = null): void
{
if (null === $dc || !$dc->id || 'edit' !== $this->requestStack->getCurrentRequest()->query->get('act')) {
return;
}
$element = ContentModel::findById($dc->id);
if (null === $element || 'my_content_element' !== $element->type) {
return;
}
$GLOBALS['TL_DCA']['tl_content']['fields']['text']['eval']['mandatory'] = false;
}
}
config.oncreate
Executed when a new record is created.
string
Tableinteger
Insert IDarray
Fields of the new record\Contao\DataContainer
Data Container objectreturn: void
config.onbeforesubmit
This feature is available in Contao 5.0 and later.
Executed when a back end form is submitted before the record will be updated in the database. Allows you to e.g. modify the values or introduce validation accross multiple fields. You are expected to return the values.
array
Values of the record\Contao\DataContainer
Data Container objectreturn: array
Values of the record
config.onsubmit
Executed when a back end form is submitted after the record has been updated in the database. Allows you to e.g. modify the record afterwards (used to calculate intervals in the calendar extension).
\Contao\DataContainer
Data Container objectreturn: void
\Contao\FrontendUser
The front end user instance\Contao\ModulePersonalData
The front end module instancereturn: void
config.ondelete
Executed before a record is removed from the database.
DC_Folder
(e.g. tl_files
)string
The path of the file\Contao\DataContainer
Data Container objectreturn: void
\Contao\DataContainer
Data Container objectinteger
The ID of the tl_undo
database recordreturn: void
This example also removes a record from a different table, if a front end member is deleted in the back end.
// src/EventListener/DataContainer/MemberDeleteCallbackListener.php
namespace App\EventListener\DataContainer;
use Contao\CoreBundle\ServiceAnnotation\Callback;
use Contao\DataContainer;
use Doctrine\DBAL\Connection;
/**
* @Callback(table="tl_member", target="config.ondelete")
*/
class MemberDeleteCallbackListener
{
private $db;
public function __construct(Connection $db)
{
$this->db = $db;
}
public function __invoke(DataContainer $dc, int $undoId): void
{
if (!$dc->id) {
return;
}
$this->db->delete('tl_foobar', ['member' => (int) $dc->id]);
}
}
config.oncut
Is executed after a record has been moved to a new position.
\Contao\DataContainer
Data Container objectreturn: void
config.oncopy
Executed after a record has been duplicated.
integer
Insert ID\Contao\DataContainer
Data Container objectreturn: void
config.oncreate_version
Executed after the old version of the record has been added to tl_version
.
string
Tableinteger
Parent ID of the tl_version
entryinteger
Version numberarray
Record datareturn: void
config.onrestore
Executed after a record has been restored from an old version.
This callback is deprecated and will be removed in Contao 5.0. Use config.onrestore_version instead.
integer
Parent ID of the tl_version
entrystring
Tablearray
Record datainteger
Version numberreturn: void
config.onrestore_version
Executed after a record has been restored from an old version.
string
Tableinteger
Parent ID of the tl_version
entryinteger
Version numberarray
Record datareturn: void
config.onundo
Executed after a deleted record has been restored from the “undo” table.
string
Tablearray
Record data\Contao\DataContainer
Data Container objectreturn: void
config.oninvalidate_cache_tags
This feature is available in Contao 4.7 and later.
This callback is executed whenever a record is changed in any way via the Contao back end. It allows you to add additional cache tags that should be invalidated.
\Contao\DataContainer
Data Container objectarray
Tagsreturn: array
An array of cache tags to be invalidated
config.onshow
This feature is available in Contao 4.7 and later.
Allows you to customize the info modal window of a database record.
array
Existing modal window dataarray
Record data\Contao\DataContainer
Data Container objectreturn: array
An array containing the table rows and columns for the modal
window.
All listing callbacks are singular callbacks - meaning there can only be one callback, not multiple ones.
list.sorting.paste_button
Allows for individual paste buttons and is e.g. used in the site structure to disable buttons depending on the user’s permissions (requires an additional command check via load_callback).
\Contao\DataContainer
Data Container objectarray
Record datastring
Tablebool
Whether this is a circular reference of the tree viewarray
Clipboard dataarray
/null
Childrenstring
/null
“Previous” labelstring
/null
“Next” labelreturn: string
HTML for additional buttons
list.sorting.child_record
Defines how child elements are rendered in “parent view”.
array
Record datareturn: string
HTML for the child record
list.sorting.header
Allows for individual labels in header of “parent view”.
array
Current header labels\Contao\DataContainer
Data Container objectreturn: array
Header labels
// src/EventListener/DataContainer/CalendarHeaderCallback.php
namespace App\EventListener\DataContainer;
use Contao\CoreBundle\ServiceAnnotation\Callback;
use Contao\DataContainer;
use Doctrine\DBAL\Connection;
/**
* Adds the total number of events to the header fields.
*
* @Callback(table="tl_calendar_events", target="list.sorting.header")
*/
class CalendarHeaderCallback
{
/** @var Connection */
private $db;
public function __construct(Connection $db)
{
$this->db = $db;
}
public function __invoke(array $labels, DataContainer $dc): array
{
$count = $this->db->fetchOne("SELECT COUNT(*) FROM tl_calendar_events WHERE pid = ?", [$dc->id]);
$labels['Total events'] = $count;
return $labels;
}
}
list.sorting.panel_callback.subpanel
This callback allows you to inject HTML for custom panels. Replace subpanel
wit your custom panel’s name.
\Contao\DataContainer
Data Container objectreturn: string
HTML for panel
list.label.group
Allows for individual group headers in the listing.
string
Groupstring
Modestring
Fieldarray
Record data\Contao\DataContainer
Data Container objectreturn: string
The group to be grouped by
list.label.label
Allows for individual labels in the listing and is e.g. used in the user module to add status icons.
array
Record datastring
Current label\Contao\DataContainer
Data Container objectstring
Always emptybool
Always falsebool
Whether the record is protectedreturn: string
The record label
array
Record datastring
Current label\Contao\DataContainer
Data Container objectarray
Columns with existing labelsreturn: If the DCA uses showColumns
then the return value must be an array
of strings. Otherwise just the label as a string
.
array
Record datastring
Current label\Contao\DataContainer
Data Container objectreturn: string
The record label
This example adds an icon to the label of the example entity as tree view.
// src/EventListener/DataContainer/ExampleLabelCallbackListener.php
namespace App\EventListener\DataContainer;
use Contao\CoreBundle\ServiceAnnotation\Callback;
use Contao\DataContainer;
use Contao\Image;
/**
* @Callback(table="tl_example", target="list.label.label")
*/
class ExampleLabelCallbackListener
{
public function __invoke(array $row, string $label, DataContainer $dc, string $imageAttribute = '', bool $returnImage = false, ?bool $isProtected = null): string
{
$icon = Image::getHtml('bundles/app/images/example.svg');
return $icon.sprintf(' %s <span class="tl_gray" style="margin-left:3px;">[%s]</span>', $row['title'], $row['name']);
}
}
This example translates a dynamic status field for the label of the example entity as list view.
// src/EventListener/DataContainer/ExampleLabelCallbackListener.php
namespace App\EventListener\DataContainer;
use Contao\CoreBundle\ServiceAnnotation\Callback;
use Contao\DataContainer;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @Callback(table="tl_example", target="list.label.label")
*/
class ExampleLabelCallbackListener
{
private TranslatorInterface $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
public function __invoke(array $row, string $label, DataContainer $dc, array $labels): array
{
$fieldName = 'status';
$fields = $GLOBALS['TL_DCA'][$dc->table]['list']['label']['fields'];
$key = array_search($fieldName, $fields, true);
$labels[$key] = $this->translator->trans('tl_example.status_option.'.$labels[$key], [], 'contao_tl_example') ?? $this->translator->trans('tl_example.status_option_unknown', [], 'contao_tl_example');
return $labels;
}
}
All operations callbacks are singular callbacks - meaning there can only be one callback, not multiple ones.
The following is a list of button callbacks for operations. Replace <OPERATION>
in each case with the actual operation you want to use the callback for.
These callbacks allow for individual navigation icons and is e.g. used in the site structure to disable buttons depending on the user’s permissions (requires an additional command check via load_callback).
list.global_operations.<OPERATION>.button
string
/null
Button hrefstring
Labelstring
Titlestring
Classstring
HTML attributesstring
Tablearray
IDs of all root recordsreturn: string
HTML for the button
list.operations.<OPERATION>.button
array
Record datastring
/null
Button hrefstring
Labelstring
Titlestring
/null
Iconstring
HTML attributesstring
Tablearray
IDs of all root recordsarray
IDs of all child recordsbool
Whether this is a circular reference of the tree viewstring
“Previous” labelstring
“Next” label\Contao\DataContainer
Data Container objectreturn: string
HTML for the button
This example hide a custom operation button if the user is not allowed to use it.
Attention: this won’t disable the operation itself, it only hides the button!
To disable the operation, you need to check for the permission additionally
before its execution, for example in the operation code or a config.onload
callback.
// src/EventListener/DataContainer/ExampleListOperationListener.php
namespace App\EventListener\DataContainer;
use Contao\Backend;
use Contao\CoreBundle\DependencyInjection\Attribute\AsCallback;
use Contao\DataContainer;
use Contao\Image;
use Contao\StringUtil;
use Symfony\Component\Security\Core\Security;
#[AsCallback(table: 'tl_example', target: 'list.operations.custom.button')]
class ExampleListOperationListener
{
public function __construct(
protected Security $security
)
{}
public function onListCustomOperationCallback(
array $row,
?string $href,
string $label,
string $title,
?string $icon,
string $attributes,
string $table,
array $rootRecordIds,
array $childRecordIds,
bool $circularReference,
string $previous,
string $next,
DataContainer $dc
): string
{
if (!$this->security->isGranted('contao_user.example', 'custom_operation')) {
return '';
}
return sprintf(
'<a href="%s" title="%s"%s>%s</a> ',
Backend::addToUrl($href . '&id=' . $row['id']),
StringUtil::specialchars($title),
$attributes,
Image::getHtml($icon, $label)
);
}
}
The following is a list of callbacks for DCA fields. Replace <FIELD>
with a
field name of your choice.
fields.<FIELD>.options
The fields.<FIELD>.options
callback is a singular callback - meaning there can
only be one callback, not multiple ones.
Allows you to define an individual function to load data into a drop-down menu or checkbox list. Useful e.g. for conditional foreinKey-relations.
\Contao\DataContainer
/null
Data Container objectreturn: array
Array of available options
fields.<FIELD>.input_field
The fields.<FIELD>.input_field
callback is a singular callback - meaning there
can
only be one callback, not multiple ones.
Allows for the creation of individual form fields and is e.g. used in the back end module “personal data” to generate the “purge data” widget. Attention: the field is not saved automatically!
\Contao\DataContainer
Data Container objectstring
Extended labelreturn: string
HTML output for the field
fields.<FIELD>.load
Executed when a form field is initialized and can e.g. be used to load a default value.
mixed
Currently stored value\Contao\DataContainer
Data Container objectreturn: mixed
New value to be loaded
mixed
Currently stored value\Contao\FrontendUser
The front end user instance\Contao\ModulePersonalData
The front end module instancereturn: mixed
New value to be loaded
fields.<FIELD>.save
Executed when a field is submitted and can e.g. be used to add an individual
validation routine. If the new value does not validate, you can throw an
\Exception
with an appropriate error message. The record will not be saved
then and the error message will be shown in the form.
mixed
Value to be saved\Contao\DataContainer
Data Container objectreturn: mixed
New value to be saved
mixed
Value to be saved\Contao\FrontendUser
The front end user instance\Contao\ModulePersonalData
The front end module instancereturn: mixed
New value to be saved
mixed
Value to be savedreturn: mixed
New value to be saved
// src/EventListener/DataContainer/ContentTextSaveCallback.php
namespace App\EventListener\DataContainer;
use Contao\CoreBundle\ServiceAnnotation\Callback;
use Contao\DataContainer;
/**
* @Callback(table="tl_content", target="fields.text.save")
*/
class ContentTextSaveCallback
{
public function __invoke($value, DataContainer $dc)
{
// Show an error if tl_content.text contains "foobar"
if (false !== stripos($value, 'foobar')) {
throw new \Exception('String "foobar" is not allowed.');
}
// Or process the value before saving
$value = strtoupper($value);
// Return the processed value
return $value;
}
}
fields.<FIELD>.wizard
Allows you to add additional HTML after the field input, typically used to show a button that starts a “wizard”.
\Contao\DataContainer
Data Container objectreturn: string
HTML for the button
fields.<FIELD>.xlabel
Allows you to add additional HTML after the field label, typically used to show a button for an import “wizard”.
\Contao\DataContainer
Data Container objectreturn: string
HTML for the button
fields.<FIELD>.eval.url
Allows you to add an url to the serp preview field.
\Contao\Model
Model object (class from the table)return: string
URL for the serp preview
// src/EventListener/DataContainer/ExampleSerpPreviewUrlCallbackListener.php
namespace App\EventListener\DataContainer;
use App\Model\ExampleCategoryModel;
use App\Model\ExampleModel;
use Contao\Config;
use Contao\CoreBundle\ServiceAnnotation\Callback;
use Contao\PageModel;
/**
* @Callback(table="tl_example", target="fields.serpPreview.eval.url")
*/
class ExampleSerpPreviewUrlCallbackListener
{
public function __invoke(ExampleModel $model): string
{
/** @var ExampleCategoryModel $category */
$category = $model->getRelated('pid');
if (null === $category) {
throw new \Exception('Invalid category');
}
/** @var PageModel $page */
$page = $category->getRelated('jumpTo');
if (null === $page) {
throw new \Exception('Invalid jumpTo page');
}
$suffix = $page->getAbsoluteUrl(Config::get('useAutoItem') ? '/%s' : '/items/%s');
return sprintf(preg_replace('/%(?!s)/', '%%', $suffix), $model->alias ?: $model->id);
}
}
fields.<FIELD>.eval.title_tag
Allows you to modify the title tag of the serp preview field.
\Contao\Model
Model object (class from the table)return: string
title tag for the serp preview
// src/EventListener/DataContainer/ExampleSerpPreviewTitleTagCallbackListener.php
namespace App\EventListener\DataContainer;
use App\Model\ExampleCategoryModel;
use App\Model\ExampleModel;
use Contao\Controller;
use Contao\CoreBundle\ServiceAnnotation\Callback;
use Contao\LayoutModel;
use Contao\PageModel;
/**
* @Callback(table="tl_example", target="fields.serpPreview.eval.title_tag")
*/
class ExampleSerpPreviewTitleTagCallbackListener
{
public function __invoke(ExampleModel $model): string
{
/** @var ExampleCategoryModel $category */
$category = $model->getRelated('pid');
if (null === $category) {
return '';
}
/** @var PageModel $page */
$page = $category->getRelated('jumpTo');
if (null === $page) {
return '';
}
$page->loadDetails();
/** @var LayoutModel $layout */
$layout = $page->getRelated('layout');
if (null === $layout) {
return '';
}
global $objPage;
$objPage = $page;
return Controller::replaceInsertTags(str_replace('{{page::pageTitle}}', '%s', $layout->titleTag ?: '{{page::pageTitle}} - {{page::rootPageTitle}}'));
}
}
The following is a list of callbacks relating to edit actions of a Data Container.
edit.buttons
Allows you to modify the action buttons at the bottom of record editing form. This can be used to add additional buttons or remove any of the existing buttons.
array
Array of strings\Contao\DataContainer
Data Container objectreturn: array
Array of strings containing the buttons’ markup
This example removes the “Save and close” button from the editing form of a record
of the tl_example
Data Container.
// src/EventListener/DataContainer/EditButtonsCallbackListener.php
namespace App\EventListener\DataContainer;
use Contao\CoreBundle\ServiceAnnotation\Callback;
use Contao\DataContainer;
/**
* @Callback(table="tl_example", target="edit.buttons")
*/
class EditButtonsCallbackListener
{
public function __invoke(array $buttons, DataContainer $dc): array
{
// Remove the "Save and close" button
unset($buttons['saveNclose']);
return $buttons;
}
}
The following is a list of callbacks relating to select actions of a Data Container.
select.buttons
Allows you to modify the action buttons at the bottom after selecting rows. This can be used to add additional buttons or update and remove any of the existing buttons.
array
Array of strings\Contao\DataContainer
Data Container objectreturn: array
Array of strings containing the buttons’ markup
This example removes the “delete” button from the editing form of a record
of the tl_example
Data Container.
// src/EventListener/DataContainer/SelectButtonsCallbackListener.php
namespace App\EventListener\DataContainer;
use Contao\CoreBundle\ServiceAnnotation\Callback;
use Contao\DataContainer;
/**
* @Callback(table="tl_example", target="select.buttons")
*/
class SelectButtonsCallbackListener
{
public function __invoke(array $buttons, DataContainer $dc): array
{
// Remove the delete button
unset($buttons['delete']);
return $buttons;
}
}