Open-Source

Consent-Management, das im CMS lebt statt daneben

Consent-Management als Open-Source-Plugin für Payload CMS. Kategorien, Dienste und Cookie-Tabelle pflegst du im Admin-Panel, in dem du ohnehin arbeitest. Keine Lizenz pro Domain, kein zweites Skript im kritischen Pfad, keine Daten über einen fremden Anbieter.

Zeigt die Liste der konfigurierte Consent Kategorien. Im Bild: Essenziell, Funktional, Statistik, Marketing
01Ausgangslage

Warum ich das Banner selbst gebaut habe

Ein Consent-Banner ist keine Funktion, die man sich wünscht. Es ist eine Pflicht, die man erfüllt und trotzdem hängt an ihr mehr, als es aussieht: Es lädt vor allem anderen, es entscheidet, ob deine Analytics überhaupt Daten sehen, und es steht als erstes zwischen deiner Seite und den Menschen, die sie besuchen.

Für meine eigenen Payload-Projekte hat mich keine der fertigen Lösungen überzeugt. Nicht wegen einzelner Features, sondern wegen der Bauweise.

  1. 01

    Zwei Systeme für eine Website. Kategorien und Texte pflegst du in einem fremden Backend mit eigenem Login, der Rest der Seite liegt im CMS.

  2. 02

    Ein zweites Skript im kritischen Pfad. Es lädt von einem fremden Server, vor allem anderen, und wenn es langsam ist, ist deine Seite es auch.

  3. 03

    Lizenzkosten pro Domain. Bei einem Kunden mit fünf Projekten ist das eine wiederkehrende Rechnung für ein Banner.

  4. 04

    Die Cookie-Tabelle in der Datenschutzerklärung driftet ab. Sie wird von Hand gepflegt und weiß irgendwann nicht mehr, was das Banner tatsächlich lädt.

  5. 05

    Reject ist kleiner, grauer oder einen Klick tiefer als Accept — und damit genau der Punkt, an dem eine Einwilligung regelmäßig als unwirksam gilt.

Nichts davon ist ein Grund, ein eigenes CMP zu bauen. Alles zusammen war einer.

02Umsetzung

Vom Pflichtbaustein zum veröffentlichten Plugin

Entstanden ist das Plugin aus dem eigenen Bedarf: Diese Website läuft selbst damit. Was erst als Bauteil für ein Projekt gedacht war, ist unterwegs ein Paket geworden, das auch außerhalb funktionieren muss. Mit Dokumentation, Tests und einer öffentlichen API, die man nicht jede Woche umbaut.

  1. wenige Stunden

    01

    Das Problem sauber schneiden

    Ein CMP ist kein Scanner. Es crawlt die Seite nicht, entdeckt keine Cookies und pflegt keine Anbieter-Datenbank, was das Banner behauptet, hast du eingetragen. Diese Grenze früh zu ziehen hat das Datenmodell einfach gehalten: Kategorien, Dienste, Einstellungen. Mehr braucht es nicht.

    ErgebnisAbgrenzung & Datenmodell

  2. der eigentliche Kern

    02

    Gating, das den Namen verdient

    Während des Renderings wird nichts injiziert. Die Dienst-Definitionen gehen als Daten in den Browser, das <script>-Element entsteht erst, wenn die Kategorie freigegeben ist. Ein Skript, dem nie zugestimmt wurde, war nie im Dokument. Dazu ein blockierendes Inline-Skript im Head, das die Entscheidung vor dem ersten Paint liest, deshalb flackert nichts.

    ErgebnisSkript-Loader & Pre-Paint-Bootstrap

  3. laufend

    03

    Gegen beide Enden der unterstützten Versionen testen

    Integrationstests laufen gegen eine MongoDB, End-to-End-Tests mit Playwright gegen eine echte Payload-App. Verifiziert wird gegen die älteste und die neuste unterstützte Payload-Version, weil ein Consent-Plugin, das bei einem Minor-Update ausfällt, teurer ist als gar keines.

    ErgebnisVitest-Integration & Playwright-E2E

  4. seit 08/2026 öffentlich

    04

    Veröffentlichen und die Kanten dokumentieren

    Die README erklärt nicht nur, was funktioniert, sondern auch, was ein CMP grundsätzlich nicht leisten kann und wo du selbst eine Entscheidung treffen musst.

    Ergebnisv1.0 auf npm, MIT-Lizenz

03Das Plugin

Was dabei herauskam

Ein Payload-Plugin, das mit consentPlugin() in der Config registriert wird, beim ersten Start vier Kategorien anlegt — Essenziell, Funktional, Statistik, Marketing — und ab dann aus dem Admin-Panel heraus gepflegt wird. Frontend-seitig genügen <ConsentProvider>, <ConsentBanner> und der mitgelieferte Stylesheet-Import.

  • Alles im CMS

    Kategorien, Dienste, Cookie-Deklarationen und die komplette Banner-Texte sind normale Payload-Dokumente. Kein zweites Backend, kein zweiter Login, kein Copy-Paste zwischen zwei Systemen.

  • Google Consent Mode v2

    Standardmäßig aktiv. Die Defaults stehen als Inline-Skript vor allem anderen im Head. Garantierbar nur, weil das Plugin den Google Tag Manager selbst nachlädt.

  • Global Privacy Control

    Standardmäßig respektiert. Sendet ein Browser das Signal und liegt noch keine Entscheidung vor, gilt alles Nicht-Essenzielle als abgelehnt und das Banner erscheint nicht. Diese abgeleitete Entscheidung wird nie in den Cookie geschrieben.

  • Einbettungen einzeln freigeben

    <ConsentGate> hält Videos, Karten oder Buchungs-Widgets zurück und zeigt einen Platzhalter mit zwei Optionen: einmal laden oder dauerhaft erlauben. Letzteres gibt bei einem CMS-Dienst die ganze Kategorie frei, ein Standalone-Gate merkt sich die Freigabe einzeln. Ein per CSS verstecktes iframe lädt trotzdem: Gating muss das DOM steuern, nicht die Darstellung.

  • Widerruf räumt auf

    Beim Widerruf werden die für den Dienst deklarierten Cookies sowie localStorage-, sessionStorage- und indexedDB-Einträge gelöscht, die Consent-Signale springen auf denied, und die Seite lädt neu, pro Dienst abschaltbar.

  • Cookie-Tabelle ohne Drift

    <CookieTable /> in der Datenschutzerklärung liest dieselben Datensätze, die auch das Banner benutzt. Die Tabelle kann nicht mehr auseinanderlaufen mit dem, was tatsächlich lädt.

  • Nachweisbar ohne Datensammeln

    Serverseitig wird über Besucher:innen standardmäßig nichts gespeichert. Stattdessen sind beide Konfigurations-Collections und das Einstellungs-Global versioniert, die Konfigurationshistorie zeigt, was das Banner wann angeboten hat. Ein Log einzelner Einwilligungen gibt es optional: zufällige UUID statt Identifikator, ohne IP und ohne User-Agent.

  • Vier Stufen eigenes UI

    Umstylen über CSS Custom Properties, Beschriftungen überschreiben, einzelne Bausteine ersetzen, oder das komplette Banner selbst bauen und nur den useConsent()-Hook benutzen. Gating, Speicherung und Signale bleiben in jeder Stufe unverändert.

  • MIT-Lizenz

    Öffentlich auf npm und GitHub. Keine Lizenz pro Domain, keine Staffelung nach Seitenaufrufen, keine Vertragsverlängerung.

Was das Plugin ausdrücklich nicht tut

Es ist kein Scanner. Es crawlt deine Seite nicht, entdeckt keine Cookies und pflegt keine Anbieter-Datenbank. Was das Banner behauptet, hast du eingetragen, das aktuell zu halten bleibt deine Aufgabe.

Und der Widerruf ist bewusst asymmetrisch, weil der Browser es ist: Einmal ausgeführter Fremd-Code hat eigene Timer, Listener und Globals installiert. Das <script>-Element zu entfernen macht davon nichts rückgängig. Genau deshalb lohnt sich die Cookie-Deklaration, sie ist nicht nur Dokumentation für die Datenschutzerklärung, sie ist die Löschliste.

04Ergebnis

Ein Consent-Banner ohne Fremdanbieter

Diese Website läuft mit dem Plugin. Was sich damit geändert hat:

  • Ein System statt zwei. Consent wird dort gepflegt, wo auch die Inhalte gepflegt werden.
  • Kein Fremd-Skript vor dem eigenen Inhalt. Das einzige, was vor der Seite läuft, ist ein paar Zeilen Inline-JavaScript aus dem eigenen Deployment.
  • Keine laufenden Lizenzkosten — für dieses Projekt nicht und für die nächsten auch nicht.
  • Die Cookie-Tabelle in der Datenschutzerklärung entsteht aus denselben Datensätzen wie das Banner und ist damit automatisch aktuell.

Veröffentlicht unter MIT-Lizenz. Wer Payload einsetzt, kann es einsetzen. Und wer eine Änderung braucht, muss dafür niemanden fragen.

  • Zeigt die Einstellungen für das verhalten des Consent Banners
  • Zeigt die Einstellungen für das Consent Banner
  • Zeigt die Liste der gespeicherten Consent Records, über angenommene/abgelehnte Cookies
  • Zeigt die cookie Einstellungen eines Consent service
  • Zeigt die delivery Einstellungen eines Consent service
  • Zeigt die generellen Einstellungen eines Consent service
  • Liste der konfigurierten Consent services. Im Bild: Essential runtime tag und Demo marketing tag
  • Zeigt die Liste der konfigurierte Consent Kategorien. Im Bild: Essenziell, Funktional, Statistik, Marketing

Auf GitHub ansehen

05Tech-Stack

Womit es gebaut ist

Bewusst nah an dem, was Payload-Projekte ohnehin mitbringen. Das Plugin fügt keine eigene State-Library, keinen Build-Schritt und keine Laufzeit-Abhängigkeit zu einem fremden Dienst hinzu.

  • Payload CMS

    Ab Version 3.40. Kategorien, Dienste und Einstellungen sind reguläre Collections und ein Global. Mit Versionierung, Zugriffsrechten und Lokalisierung aus dem Framework selbst.

  • React 19

    Banner, Einstellungsdialog, Gates und Cookie-Tabelle sind React-Komponenten. Der useConsent()-Hook macht sie vollständig ersetzbar.

  • Next.js

    Für den servergerenderten Einstiegspunkt. Genutzt wird genau eine Next-API, cookies() aus next/headers. Als optionaler Peer deklariert, unterstützt werden 15 und 16.

  • TypeScript

    Durchgehend typisiert, mit eigenem types-Einstiegspunkt für Projekte, die nur die Typen brauchen.

  • Vitest & Playwright

    Integrationstests gegen eine MongoDB, End-to-End-Tests gegen die echte Dev-App. Verifiziert gegen beide Enden des unterstützten Payload-Bereichs.

  • CSS Custom Properties

    Kein Styling-Framework. Jedes Element trägt ein data-pc-*-Attribut, jede Farbe eine Custom Property. Reskin ohne Spezifitätskampf, Light- und Dark-Mode über prefers-color-scheme.

06Häufige Fragen

Was Interessierte meistens wissen wollen

Ist das Plugin kostenlos?

Ja. MIT-Lizenz, öffentlich auf npm als @bespokecode/payload-consent und auf GitHub. Kommerzielle Nutzung eingeschlossen, keine Lizenz pro Domain, keine Staffelung nach Seitenaufrufen.

Was brauche ich, um es einzusetzen?

Payload 3.40 oder neuer und React 19. Für den servergerenderten Einstiegspunkt zusätzlich Next.js (15 und 16 werden beide unterstützt). Ist dein Frontend separat deployt, gibt es einen Client-Pfad, der weder Payload noch next/headers mitzieht.

Macht mich das Plugin DSGVO-konform?

Nein und kein Werkzeug kann das. Das Plugin liefert die technische Seite: Es hält nicht-essenzielle Skripte zuverlässig aus dem Dokument, gewichtet Zustimmen und Ablehnen gleich und dokumentiert die Konfigurationshistorie. Welche Dienste du einsetzt, in welche Kategorie sie gehören und was in deiner Datenschutzerklärung steht, bleibt deine Entscheidung. Rechtsberatung ist das ausdrücklich nicht.

Erkennt es meine Cookies automatisch?

Nein. Das Plugin ist kein Scanner: Es crawlt die Seite nicht und pflegt keine Anbieter-Datenbank. Was das Banner behauptet, hast du eingetragen. Das ist bewusst so. Eine automatisch erzeugte Cookie-Tabelle, die zu 80 % stimmt, ist im Zweifelsfall schlimmer als eine gepflegte.

Was passiert, wenn jemand die Zustimmung widerruft?

Die für den Dienst deklarierten Cookies sowie localStorage-, sessionStorage- und indexedDB-Einträge werden gelöscht, die Consent-Mode-Signale springen zurück auf denied, und die Seite lädt neu. Pro Dienst abschaltbar über Bei Widerruf → Nichts tun. Was bereits ausgeführter Fremd-Code angelegt hat, lässt sich nicht rückgängig machen, deshalb ist die Cookie-Deklaration gleichzeitig die Löschliste.

Werden Besucher:innen erneut gefragt, wenn ich etwas ändere?

Nur, wenn sich die Struktur ändert: welche Kategorien es gibt, welche davon essenziell sind und welche Dienste unter welcher Kategorie laufen. Ein Tippfehler in einer Beschreibung löst kein erneutes Fragen aus, ein neuer Analytics-Dienst in einer akzeptierten Kategorie schon. Und erneut fragen heißt nicht zurücksetzen: Die bisherige Auswahl füllt den Einstellungsdialog vor.

Speichert es Daten über Besucher:innen?

Standardmäßig serverseitig nichts. Ohne Login ließe sich ein Server-Log ohnehin keiner Person zuordnen und es zuordenbar zu machen hieße, IP oder Fingerprint zu erheben, also neue personenbezogene Daten zu sammeln, um zu belegen, dass man sorgsam mit personenbezogenen Daten umgeht. Wer vertraglich ein Log braucht, schaltet es mit logging: true ein: zufällige UUID, Zeitstempel, Konfigurationsversion, Auswahl, Art der Entscheidung und Seitenpfad. Keine IP, kein User-Agent. Der Datensatz ist bewusst keiner Person zuzuordnen.

Kann ich das Banner an mein Design anpassen?

In vier Stufen: umstylen über CSS Custom Properties, Beschriftungen überschreiben, einzelne Bausteine wie Platzhalter oder Einstellungsdialog ersetzen, oder das Banner komplett selbst bauen und nur den useConsent()-Hook benutzen. Gating, Speicherung, Consent-Mode-Signale und Skript-Loader bleiben in jeder Stufe unangetastet.

Fragen, die hier nicht beantwortet sind, gehören in den Issue-Tracker auf GitHub. Wer Unterstützung bei der Einrichtung, ein Review des eigenen Consent-Setups oder generell Payload-Entwicklung braucht: einfach melden.

Nächster Schritt

Consent-Setup, das dir gehört?

Ob Payload-Projekt, Consent-Review oder ein ganz anderes Digitalisierungsthema: Lass uns kurz sprechen und schauen, was in deinem Fall sinnvoll ist.

Maximilian Eckel, Gründer von BespokeCode, angelehnt an eine Glasfront in einem hellen Raum.

Du sprichst mit

Maximilian Eckel

Gründer & Geschäftsführer