Neuerungen in Contao 6

von Dennis Erdmann

Contao 6.0 ist am 26. August 2026 erschienen. Bei einem Minor-Sprung reicht meist eine Liste der neuen Funktionen – bei einer Major-Version nicht. Hier ist vieles nicht neu, sondern weg: das Input-Encoding, die HTML5-Templates, ein Teil der PHP-API und etliche JavaScript-Hilfen im Backend.

Deswegen ist diese Referenz zweigeteilt: erst das, was Redakteur:innen im Alltag merken, dann das, was Entwickler:innen anfassen müssen.

Vorweg der Hinweis, der über allem steht: Contao 6.0 ist keine LTS-Version und wird nur bis Februar 2027 gepflegt. Ob du überhaupt jetzt schon wechseln solltest, klärt Contao 5 oder 6: Welche Version soll ich verwenden?.

Was Redakteur:innen merken

Weniger Klickwege beim Verschachteln, mehr Möglichkeiten bei geschützten Dateien – und ein Menüpunkt weniger.

  • Der Menüpunkt „Templates" ist verschwunden. Contao ist vollständig auf Twig umgestellt, damit entfällt die alte Template-Verwaltung im Backend.
  • Verschachtelte Elemente direkt einfügen: Inhaltselemente lassen sich unmittelbar in Akkordeons, Elementgruppen und Slider einsetzen, ohne vorher eine Ebene tiefer zu navigieren. Dazu kommt eine „Einfügen in"-Operation in der Übersicht.
  • Geschützte Dateien in Video- und Download-Elementen: Contao erzeugt dafür automatisch temporäre URLs. Vorher war für geschützte Downloads eine Sonderlösung nötig.
  • Seitenbild in den Seiteneigenschaften: Ein neues Bildfeld, das in der Suche und im JSON-LD der Seite auftaucht.
  • JSON-LD-Typ pro Nachrichtenarchiv wählbar: NewsArticle, Article oder Blog – relevant, wenn ein Archiv eigentlich ein Blog ist.
  • Kommentare ohne BBCode: Die BBCode-Unterstützung ist entfallen, bestehende Kommentare werden zu Klartext migriert. URLs werden dafür automatisch verlinkt.
  • Passwort ändern nur noch im richtigen Modul: Im Modul „Persönliche Daten" geht das nicht mehr, dafür braucht es das Modul „Passwort ändern". Wer in einem Mitgliederbereich bisher nur „Persönliche Daten" eingebunden hat, muss das Modul ergänzen – sonst können Mitglieder ihr Passwort nach dem Update nicht mehr ändern.
  • Backend-Breite sitzt jetzt in den Backend-Einstellungen statt im Benutzerprofil.
  • Barrierefreiheit verbessert: ARIA-Labels dort, wo kein <label> existiert, und der aktive Eintrag in der Breadcrumb wird als <strong> ausgezeichnet.
  • Nur noch ein Backend-Theme. Mehrere Backend-Themes sind nicht mehr möglich.

Die eine große Änderung: Das Input-Encoding fällt weg

Das ist die Umstellung, aus der die meisten anderen folgen – und die einzige, die auch Redakteur:innen indirekt trifft.

Bisher hat Contao Eingaben beim Speichern kodiert: Aus < wurde &lt; in der Datenbank. Ab Contao 6 landen die Werte roh in der Datenbank, und die Kodierung passiert erst bei der Ausgabe.

  • Warum das gut ist: Derselbe Inhalt lässt sich in verschiedenen Kontexten wiederverwenden – HTML, JSON-LD, XML-Feeds, APIs. Vorher war der Inhalt auf HTML festgelegt.
  • Was das im Alltag bedeutet: Läuft alles sauber über Twig, merkst du nichts. Taucht nach dem Update irgendwo &amp; oder &lt; im sichtbaren Text auf, ist das der Hinweis auf eine Stelle, die noch selbst kodiert – meist eine Erweiterung oder ein eigenes Template.
  • Für die Bestandsdaten gibt es eine Migration. Die läuft beim Update mit und stellt die vorhandenen Inhalte um.
  • Double Encoding ist jetzt standardmäßig aktiv. Die Escape-Filter contao_html und contao_html_attr sowie die Contao-Escaper-Strategie, die das früher verhindert haben, sind entfernt.
  • decodeEntities und useRawRequestData sind entfallen. HTML erlaubst du weiterhin über allowHtml; bei Feldern mit rte wird HTML automatisch bereinigt, abschalten lässt sich das über preserveTags.

Was Entwickler:innen anfassen müssen

Ausgabe kodieren

  • Gibst du HTML aus einem Hook, Callback oder Event-Listener zurück, ohne über Twig zu gehen: mit StringUtil::specialchars() oder htmlspecialchars() kodieren.
  • Enthält eine Eingabe HTML, das erhalten bleiben soll: Twig-Filter sanitize_html('contao') oder den Service contao.html_sanitizer.
  • |raw ist jetzt gefährlicher als vorher. Alles, was du damit ausgibst, muss vertrauenswürdig sein. Im Zweifel |sanitize_html('contao') verwenden.
  • Brauchst du Escaping ohne Double Encoding, übergibst du false als drittes Argument an StringUtil::specialchars().

Templates

  • .html5-Templates funktionieren nicht mehr. Das Legacy-Template-System ist entfernt, alles läuft über Twig.
  • Der Twig-Namespace @Contao_Global heißt jetzt @Contao_User. In den meisten Fällen brauchst du ihn ohnehin nicht explizit – nimm den verwalteten @Contao-Namespace.
  • Der Filter |insert_tag_raw heißt jetzt |insert_tag_html und erwartet vorkodierte Eingaben. Aus foo|insert_tag_raw|raw wird also foo|raw|insert_tag_html – besser gleich foo|sanitize_html('contao')|insert_tag_html.
  • Die verbliebenen Symfony-Bundle-Templates sind weg:
    • @ContaoCore/Error/layout.html.twig@Contao/error/_layout.html.twig
    • @ContaoCore/Image/Studio/_macros.html.twig@Contao/component/_figure.html.twig
    • @ContaoCore/Image/Studio/figure.html.twig@Contao/component/_figure.html.twig
    • @ContaoCore/blank.html.twig → ersatzlos
  • Der FigureRenderer und der figure-Insert-Tag rendern jetzt standardmäßig component/_figure.

DCA und Models

  • child_record_callback ist entfernt, nutze stattdessen label_callback.
  • Der Rückgabewert des label_callback wird jetzt HTML-kodiert. Brauchst du echtes HTML, gib ein RecordLabel-Objekt mit gesetztem htmlLabel zurück.
  • Models casten Werte automatisch auf den passenden Typ. Lässt sich ein Wert nicht konvertieren, fliegt eine Exception: $contentModel->id = '123' wird zur Ganzzahl, 'not_an_int' wirft.
  • Fehlende Werte liefern jetzt den Default statt null. Das ist die Änderung, die am leisesten kaputtgeht – prüfe eigenen Code auf === null und is_null().
  • SQL-Definitionen sind auf Array-Notation umgestellt.
  • tl_member.language enthält keine Ländercodes mehr. Brauchst du Locales wie de_AT, stellst du das per #[AsCallback('tl_member', target: 'fields.language.options')] wieder her.

PHP-API

  • Widget::generate() gibt es bei Frontend-Formular-Widgets nicht mehr – nutze Widget::parse().
  • Header, die per header() gesetzt werden, werden nicht mehr in die Symfony-Response gemerged. Arbeite mit dem Response-Objekt.
  • Die Messenger-Marker-Interfaces HighPriorityMessageInterface, NormalPriorityMessageInterface und LowPriorityMessageInterface sind entfallen. Stattdessen: #[AsMessage('contao_prio_high|contao_prio_normal|contao_prio_low')].
  • Swift Mailer wird nicht mehr unterstützt.
  • Doctrine DBAL 3 wird nicht mehr unterstützt.
  • Entfernt: der RenderPageEvent, der FilterPageTypeEvent und der EntityCacheTags-Service.

JavaScript im Backend

Die Rückwärtskompatibilitäts-Schichten sind raus, Ersatz sind Stimulus-Attribute und -Controller:

  • Backend.enableToggleSelectdata-contao--check-all-target="input"
  • Theme.setupCtrlClickdata-contao--deeplink-target="primary"
  • Backend.initScrollOffset / Backend.getScrollOffsetdata-contao--scroll-offset-target="scrollTo"
  • AjaxRequest.toggleFieldset → Controller contao--toggle-fieldset
  • Theme.setupTextareaResizing → Controller contao--textarea-autogrow
  • Theme.setupMenuToggle, Theme.setupProfileToggle, Theme.setupSplitButtonToggle → Controller contao--toggle-receiver und contao--toggle-sender
  • Alle übrigen Theme.*-Methoden sind ersatzlos entfernt.

Backend-Anpassungen laufen jetzt über die Konfigurationsoptionen contao.backend.custom_css und contao.backend.custom_js.

Dateisystem

  • Die DBAFS nutzt jetzt einen 128-Bit-xxHash statt md5. Sorge vor dem Update dafür, dass Datenbank und Dateisystem synchron sind, etwa mit contao:filesync. Die alten md5-Hashes entfernt eine Migration; neu aufgebaut werden sie bei der ersten Synchronisierung nach dem Update.
  • Der {{file::*}}-Insert-Tag basiert jetzt auf dem Virtual File System und kann mit geschützten Dateien umgehen.
  • Das Log-Verzeichnis heißt var/log statt var/logs.

Schon jetzt deprecated – Ziel ist Contao 7

Das läuft in Contao 6 noch, verschwindet aber in der nächsten Major-Version. Wer jetzt sowieso Hand anlegt, erledigt es gleich mit:

  • Die Input-Klasse – nutze das Request-Objekt.
  • Hybrid, ContentElement und Module – nutze Fragment-Controller.
  • Contaos Service-Annotationen – nutze PHP-Attribute, also #[AsCallback(…)] statt /** @Callback(…) */.
  • $GLOBALS['objPage'] und global $objPage – nutze contao.routing.page_finder.
  • $GLOBALS['TL_LANGUAGE'] – nutze die Locale des Requests.
  • Der <base>-Tag – nutze absolute Pfade für Links und Assets.
  • Die Twig-Filter |insert_tag_raw und |input_encoded_to_plain_text.

Darauf kannst du dich verlassen: Contao folgt Semantic Versioning, und laut der offiziellen VERSIONING.md muss jede inkompatible API-Änderung in einer neuen Major-Version erscheinen. Die Punkte oben überstehen also 6.1, 6.2 und auch die LTS 6.3.

Für Templates gilt das ausdrücklich nicht. Sie zählen laut derselben Regel nicht zur API und dürfen sich in Minor- und sogar in Bugfix-Releases ändern – auch dann, wenn das ein angepasstes Template zerlegt oder CSS-Nacharbeit am neuen Markup erzwingt. Die Zusage „bis Contao 7 passiert nichts" gilt für die PHP-API, nicht für dein Theme.

Systemvoraussetzungen

Der Sprung ist deutlicher als bei Contao 5:

  • PHP 8.4
  • Symfony 7.4 oder 8
  • Doctrine DBAL 4.4, Doctrine ORM 3.6
  • Twig 3.21
  • TinyMCE 5, 6, 7 oder 8

Gerade PHP 8.4 ist der Punkt, an dem es bei Shared Hosting klemmen kann. Das gehört vor die Update-Entscheidung, nicht mittendrin.

Vor dem Update

  1. Zuerst auf die aktuellste Contao 5.7 aktualisieren. Ein direkter Sprung von älteren Versionen funktioniert nicht, weil nur in 5.7 alle nötigen Migrations vorliegen.
  2. Dateisystem synchronisieren (contao:filesync), damit die Hash-Umstellung sauber greift.
  3. Erweiterungen prüfen. „Contao 5 kompatibel" sagt über Contao 6 nichts aus – durch den Wegfall des Input-Encodings und der HTML5-Templates ist der Anpassungsbedarf real.
  4. Nach dem Update noch einmal eine Dateisystem-Synchronisierung laufen lassen.

Siehe auch:

  • contao6
  • updates
  • features
  • backend
  • entwicklung
  • migration

Zurück zur Übersicht