Adding Custom Fields to Contao’s Backend with the Data Container Array
Learn how Contao’s Data Container Array lets you declare custom backend fields declaratively, with a worked example, cache caveats, and schema trade-offs.
12 Sept 2026, 23:37 UTC

If you have ever needed a custom field in the Contao back end—say a numeric rating on a content element—you might be tempted to edit core templates or write a bespoke form from scratch. There is a more maintainable path: the Data Container Array, or DCA. Declaring the field in PHP lets Contao render the input, handle validation, and map it to the database without you touching raw HTML. The trade-off is that you must respect Contao’s array structure, palette system, and cache lifecycle.
Why declare fields instead of hacking templates
Contao builds its backend forms from a declarative configuration rather than hand-written HTML. When you add a field by editing a core template, you couple your project to Contao’s internal markup, and the next core update can silently break your change. The DCA keeps customizations inside your own module or app directory, so upgrades remain predictable.
How the DCA maps declarations to the backend
The DCA is a global PHP array, $GLOBALS['TL_DCA'], keyed by database table. For the content-element table, the key is tl_content. Inside that entry, the fields sub-array declares each input: its label, widget type, validation rules, and the SQL column definition. The palettes sub-array controls which fields appear on the edit screen and how they are grouped into legends. A field can be fully defined and still invisible until you add it to a palette.
Two keys do most of the daily work. inputType selects the widget, such as text or select. eval is an array of rendering and validation hints—CSS classes, maximum length, and regular-expression checks like 'rgxp' => 'digit'. The sql key describes the column so that Contao’s install tool can create it, though you still need to apply the schema change yourself.
Worked example: a numeric rating on tl_content
Suppose you want editors to assign a 1–5 rating to text content elements. Create a DCA file in your module, for example app/Resources/contao/dca/tl_content.php.
<?php
$GLOBALS['TL_DCA']['tl_content']['fields']['rating'] = [
'label' => ['Rating', 'Enter a value between 1 and 5.'],
'exclude' => true,
'inputType' => 'text',
'eval' => ['tl_class' => 'w50', 'maxlength' => 1, 'rgxp' => 'digit'],
'sql' => "int(10) unsigned NOT NULL default '0'",
];
The exclude flag hides the field from users who lack explicit permission. Next, inject the field into the palette for the text element type. A common pattern is to append it after the protected checkbox:
<?php
$GLOBALS['TL_DCA']['tl_content']['palettes']['text'] = str_replace(
'protected;',
'protected;{rating_legend},rating;',
$GLOBALS['TL_DCA']['tl_content']['palettes']['text']
);
Because this relies on string replacement, a future Contao release that renames or reorders the protected segment could leave your field orphaned. After any core upgrade, inspect the resulting palette to confirm the injection still succeeds.
Create the database column
The DCA declaration does not alter your database. Add the matching column with SQL, or preferably through your project’s migration tooling so the change is version-controlled:
ALTER TABLE tl_content ADD COLUMN rating int(10) unsigned NOT NULL DEFAULT '0';
Run this in your database client. The executing user needs ALTER privileges on the table. On large tl_content tables, the statement can lock writes briefly, so test in staging first.
Clear the cache
Contao caches the compiled DCA, so your new field will not appear until the cache is cleared. From the project root, run:
php vendor/bin/contao-console cache:clear
The user running this command needs write access to the cache directory, typically var/cache. Expect the next backend request to be slower while the cache rebuilds. If you cannot use the console, clear the cache through the back end under System → Maintenance.
Trade-offs and maintenance reality
The DCA is powerful, but it is not free. The array structure is large and version-sensitive: key names and accepted values can differ between Contao 4 and Contao 5, so always confirm syntax against the documentation for your installed version. Adding many custom fields to a single table can turn a DCA file into a maintenance liability, especially when several developers edit the same palette strings. The cache-clear step is another friction point—forgetting it makes developers think their code is broken when it simply has not been recompiled.
Finally, the DCA only describes the backend form and the expected column. It does not migrate your database, sanitize business logic beyond the built-in validators, or automatically expose the field to the front end. You remain responsible for schema management and for any template that should display the value.
Verifying the result
Open a text content element in the Contao back end. Confirm that a Rating field appears under a new legend. Enter 3, save, and query the database to verify the rating column contains 3. Then enter a non-numeric character and confirm that validation rejects it, which proves the 'rgxp' => 'digit' rule is active. If the field is missing, the most common causes are an uncleared cache or a palette replacement that did not match.
Start small: add one field, clear the cache, and confirm the round trip from backend form to database. Keep each module’s DCA in its own file, lean on eval for shared settings, and treat palette string surgery as a change that needs review after every core upgrade.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.