Plugin-Entwicklung: Überblick
Ein Plugin ist ein eigenständiges Paket mit eigenem Namensraum. Es bringt Datenmodell, API, Rechte, Hintergrundverarbeitung, Migrationen und Oberfläche mit, ohne eine Zeile am Kern zu ändern.
Das ist keine Höflichkeit gegenüber dem Kern, sondern die Bedingung dafür, dass ein Systemupdate dein Plugin nicht zerlegt.
Fang hier an
Wenn du nur eine Seite lesen willst, lies den vollständigen Durchlauf. Er baut ein lauffähiges Plugin von null auf und zeigt jede Datei vollständig — Bundle, Descriptor, Entity, Migration, API-Ressource, Voter, Rechte, Oberfläche und Test. Etwa eine Stunde.
Die Regeln, die dabei gelten, stehen gesammelt in den Richtlinien, je mit der Folge, wenn man sie bricht.
Diese Seite erklärt das Warum dahinter.
Drei Bausteine tragen alles
Das Bundle lädt den Code — eine Klasse, die AbstractPlugin erweitert. Ein leerer Rumpf genügt: Die Basisklasse findet Dienste, meldet src/Entity/ als Datenmodell an, registriert src/Migrations/ und templates/.
Der Descriptor implementiert PluginDescriptorInterface: Schlüssel, Name, Version, Vertrauensstufe, der eigene Modul-Schalter und die beanspruchten API-Segmente. Er ist die einzige Pflicht-Implementierung.
Die Verträge sind der Weg, auf dem ein Plugin additiv beisteuert: Stammdaten, Rechte, Beispieldaten, Automatisierungs-Bausteine, Suche, Nummernkreise. Es gibt rund 200 davon, und ein Plugin implementiert genau die, die es braucht. Implementieren genügt; es gibt keine Registrierung.
Ein Plugin ändert nie einen Katalog des Kerns. Es liefert eigene Zeilen und meldet sich als deren Eigentümer. Genau daran erkennt das System beim Entfernen, was aufzuräumen ist.
Der Modul-Schalter sperrt im Zweifel
Jede Route eines Plugins ist nur erreichbar, wenn sein Schalter existiert und eingeschaltet ist. Fehlt die Zeile, oder lässt sich die Datenbank nicht fragen, antwortet die Route mit 404.
Das ist die umgekehrte Logik zum Kern, dessen Module ohne Eintrag als eingeschaltet gelten. Der Grund: Ein Plugin ist fremder Code. Im Zweifel ist zu ist die sichere Antwort.
Der 404 statt eines 403 ist ebenfalls Absicht. Ein 403 verriete, dass es die Route gibt.
Was ein Plugin nicht hat
Wer aus anderen Erweiterungssystemen kommt, sucht drei Dinge vergeblich:
- Keine Lebenszyklus-Methoden am Bundle. Kein
install(), keinupdate(). Teilnahme läuft über
Verträge, siehe Lebenszyklus.
- Keine Konfigurationsdatei für Dienste. Kein einziges ausgeliefertes Plugin hat eine.
- Keine eigenen Backend-Übersetzungen. Die liegen zentral. Nur die Oberfläche bringt ihre
eigenen mit.
Der Weg durch diese Doku
| Wenn du … | Lies |
|---|---|
| ein Plugin von null bauen willst | Dein erstes Plugin |
| wissen willst, was du einhalten musst | Richtlinien |
| nur die Befehle brauchst | Schnellstart |
| wissen willst, was jede Datei tut | Anatomie |
| Datenmodell, API und Rechte brauchst | Backend |
| Ansichten beisteuerst oder fremde erweiterst | Oberfläche |
| Beschriftungen übersetzt | Übersetzungen |
| verstehen willst, was beim Installieren passiert | Lebenszyklus |
| ausliefern willst | Verteilung |
| prüfen willst, ob es hält | Testen |
Dazu die erzeugten Referenzen, die direkt aus dem Code entstehen und deshalb nicht veralten können:
- Vertrags-Referenz — jeder Vertrag mit Signatur
- Andockstellen — alle Stellen der Oberfläche
- Manifest — jeder Schlüssel
- Ereignisse — was man abfangen und auslösen kann
- API und MCP — jede Operation
Zwei Sätze, die Zeit sparen
Trag jedes neue API-Präfix in den Descriptor ein. Für ein undeklariertes Segment greift keine Sperre; die Route bleibt erreichbar, auch wenn das Modul aus ist.
Ruf parent:: auf, wenn du eine Methode der Basisklasse überschreibst. Sonst fällt lautlos aus, was sie erledigt, und der Fehler zeigt sich erst bei einer Frischinstallation.