Die drei Wege: kaufen, anpassen, selbst bauen
Der erste Weg ist der Shopware Store, und er ist der richtige, wenn eine Erweiterung Ihre Anforderung im Wesentlichen trifft. Für Zahlarten, Versanddienstleister, Bewertungssysteme, Rechnungslayouts oder Marktplatz-Anbindungen gibt es gepflegte Standardlösungen, und eine Eigenentwicklung daneben wäre Verschwendung. Zwei Dinge gehören zur Kalkulation. Erstens: Seit dem 28. Dezember 2023 ist die Mietlizenz die einzige Möglichkeit, eine Erweiterungslizenz im Shopware Store zu erwerben. Endet die Mietzeit, endet das Nutzungsrecht an der Erweiterung — das ist eine laufende Position im Betriebsbudget, keine Einmalinvestition. Zweitens: Sie übernehmen die Roadmap des Herstellers. Ob es zum Release von Shopware 6.8 eine kompatible Fassung gibt, entscheidet er, nicht Sie. Wenn eine Store-Erweiterung Ihre Anforderung deckt, sagen wir das, obwohl wir daran nichts entwickeln.
Der zweite Weg ist die Anpassung einer vorhandenen Erweiterung, und er wird regelmäßig übersehen. Der Quellcode liegt dafür lesbar vor: PHP und Twig ohnehin, und die Qualitätsrichtlinien für den Shopware Store verlangen, dass die unkompilierte Fassung des JavaScript in einem eigenen Verzeichnis mitgeliefert wird. Anpassen heißt trotzdem nicht, im fremden Plugin zu editieren — dann ist beim nächsten Hersteller-Update alles wieder weg. Wir bauen ein zweites, kleines Plugin daneben: es dekoriert die Services der Fremd-Erweiterung, hängt sich an ihre Events, erweitert ihre Twig-Templates über sw_extends und überschreibt im Admin nur die Komponenten, die es ändern muss. Beide Erweiterungen bleiben getrennt aktualisierbar. Die Grenze dieses Weges ist scharf: Wenn die Fremd-Erweiterung keine Erweiterungspunkte anbietet, ihre Logik in finalen Klassen oder privaten Methoden versteckt und ihre Templates keine sinnvollen Blöcke setzt, endet der Weg. Das sehen wir vorher, nicht mitten im Projekt.
Der dritte Weg ist die Eigenentwicklung. Sie ist in der Anschaffung fast immer die teuerste und über die Laufzeit selten die falsche. Sie bekommen genau die Funktion, die Ihr Prozess braucht, keine Konfigurationsoberfläche für dreißig Fälle, die Sie nicht haben, und keine Fremdlogik im Request-Pfad des Checkouts. Kein Nutzungsrecht, das mit einer Kündigung endet. Dafür tragen Sie die Pflege selbst: Jeder Major-Sprung von Shopware kostet Anpassung, und diese Anpassung steht dann auf Ihrer Rechnung. Wir rechnen beide Wege gegeneinander, wenn Sie es verlangen, und die Zahl, die dabei zählt, ist nicht der Angebotspreis, sondern die Summe über die Jahre, die Sie das System betreiben wollen. Und manche Anforderung ist gar keine Plugin-Anforderung: Ein Plugin, das eine unklare Regel automatisiert, macht die Unklarheit schneller, nicht besser.
Plugin oder App: der Unterschied, der bleibt
Ein Plugin ist PHP-Code, der im Shopware-Prozess selbst läuft. Technisch ist es ein Composer-Paket mit einer Plugin-Basisklasse und einem Lebenszyklus aus install, activate, update, deactivate und uninstall. Es hat Zugriff auf alles, was Shopware und Symfony hergeben: Event-Subscriber, Service-Dekoration über den Container, eigene EntityDefinition und Erweiterungen bestehender Entitäten, eigene Storefront- und Admin-Routen, Twig-Overrides, eigene Admin-Komponenten, Scheduled Tasks und Message-Handler. Der Preis dafür ist Bindung: Ein Plugin lässt sich nur in einer selbst gehosteten Installation betreiben, in Shopware Cloud nicht. Und es liegt im Request-Pfad. Was es dort an Zeit verbraucht, verbraucht der Shop bei jedem Aufruf mit.
Eine App bringt im Shop keinen ausführbaren Code mit, sondern eine manifest.xml. Die Logik läuft auf einem eigenen App-Server, der über Admin API, Store API und Webhooks mit dem Shop spricht — in einer Sprache Ihrer Wahl. Apps laufen in Shopware Cloud und in selbst gehosteten Installationen. Für Logik, die im Shop-Prozess passieren muss, gibt es App Scripts: Twig-Dateien, die seit Shopware 6.4.8.0 in einer Sandbox an definierten Hooks ausgeführt werden und dort nur die Services sehen, die der jeweilige Hook bereitstellt. Die Administration erweitert eine App über das Meteor Admin SDK; die Oberfläche kommt aus einem iframe und kommuniziert per postMessage. Eigene Datenstrukturen gehen über eine Resources/entities.xml; die Entitätsnamen tragen üblicherweise das Kürzel ce_, seit Shopware 6.4.15.0 möglich und empfohlen, weil die vollen Namen an den Längengrenzen der Datenbank scheitern. Danach tauchen sie als reguläre Entität in Admin API und Administration auf.
Die Entscheidung ist unspektakulär. App, wenn Shopware Cloud im Spiel ist, wenn die Logik ohnehin außerhalb lebt — eine Preisberechnung im ERP, ein Verfügbarkeitsdienst, eine Mittelschicht zu einem Drittsystem —, oder wenn Sie die Erweiterung an mehrere Shops ausliefern wollen. Plugin, wenn tief in Datenmodell, Storefront oder Checkout eingegriffen wird, wenn Latenz zählt oder wenn die Funktion ohne Netzverbindung nach draußen laufen muss. Beide Nachteile gehören ausgesprochen: Der App-Server ist ein zweites System, das Sie betreiben, überwachen und aktuell halten müssen — steht er, steht die Funktion. Das Plugin dagegen lebt im Deployment des Shops und braucht bei jedem Major-Release Ihre Aufmerksamkeit. Es gibt hier keine Variante ohne Folgekosten, nur zwei verschiedene.
Konform entwickeln, damit das nächste Update nicht blockiert
Shopware sagt Erweiterungsentwicklern eine Rückwärtskompatibilität zu, und diese Zusage ist erfreulich konkret. Als öffentliche API gelten unter anderem öffentliche und protected Methoden, Eigenschaften und Konstanten, Interfaces und abstrakte Klassen, die Konstruktoren von DTOs wie CalculatedPrice oder QuantityPriceDefinition, Twig-Templates und -Blöcke der Storefront, die JavaScript-Plugins und -Services, die Props, Slots und Events der Admin-Komponenten sowie CSS-Selektoren mit struktureller Bedeutung. Breaking Changes sind nur in Major-Releases erlaubt; vorher wird der betroffene Code mit einer Annotation wie @deprecated tag:v6.8.0 markiert, die das Zielrelease und den Ersatz nennt. Wer diese Annotationen liest, weiß Monate vor dem Major, was zu tun ist. Zwei Ausnahmen muss man kennen. Erstens: Der Konstruktor eines Services, den der Container instanziiert, ist ausdrücklich keine öffentliche API und darf sich jederzeit ändern — wer einen solchen Service selbst per new erzeugt statt ihn zu dekorieren, baut sich die Blockade ein. Zweitens: Nicht zugesichert sind private Methoden, Eigenschaften und Konstanten sowie alles, was mit @internal oder @experimental markiert ist. Eine als final markierte Klasse gehört dagegen zur öffentlichen API — sie darf benutzt, nur nicht beerbt werden. An diesen Rändern sitzen die Plugins, die später Updates blockieren.
Praktisch heißt das eine kurze Liste. Keine Core-Datei anfassen — die Qualitätsrichtlinien für Store-Erweiterungen verbieten Änderungen an Core-Dateien und Core-Datenbanktabellen ohnehin. Event-Subscriber statt kopierter Logik. Service-Dekoration statt Klassenersatz. Entity-Extension statt ALTER TABLE. Twig-Erweiterung mit sw_extends und gezielten Block-Overrides statt eines kopierten Templates, das beim nächsten Storefront-Umbau still auseinanderfällt. Im Admin die vorgesehene Override-API statt Datei-Ersatz. Dazu die Dinge, die im Betrieb später auffallen: eine Deinstallation, die ihre Daten wieder mitnimmt, Snippets mit Fallback-Sprache, ordentliches Logging statt Ausgaben ins Nichts und Migrationen, die ein zweites Mal ohne Schaden durchlaufen.
Prüfen ist billiger als hoffen, und die Werkzeuge dafür liefert Shopware selbst mit. shopware-cli extension validate prüft in der Grundstufe Metadaten, Packaging, Verzeichnisstruktur, Snippets und PHP-Syntax; mit --full kommen PHPStan, ESLint, Stylelint sowie separate Prüfungen für Admin- und Storefront-Twig dazu, einzeln zuschaltbar über --only und --exclude. Mit --check-against lowest beziehungsweise highest läuft die Prüfung gegen die Ränder Ihres Versions-Constraints, und genau dort brechen Plugins. extension format und extension fix räumen auf, was maschinell aufräumbar ist; darunter arbeiten PHP CS Fixer, Prettier, Rector für PHP und ESLint für JavaScript. Der frühere Extension Verifier, ein eigenes Docker-Image mit den Kommandos check, format und fix, ist im Mai 2025 in shopware-cli aufgegangen — sein Repository ist archiviert, seine Prüfungen stecken in den Kommandos oben. Es sind also nicht zwei Werkzeuge, sondern eines. Bei uns läuft es in der CI, nicht auf dem Laptop eines Einzelnen. Nur: Ein Linter findet keinen Denkfehler. Ein Plugin, das jede Regel einhält und das falsche Problem löst, ist trotzdem falsch.
Der Bruch mit 6.7: zwei Linien, zwei Releases
Shopware 6.7 ist der größte technische Umbau seit dem Start von Shopware 6, und er trifft die Administration am härtesten. Vue 3 statt Vue 2 — die Kompatibilitätsschicht ist entfernt, nicht deprecated. Vite statt Webpack, damit wandert eine webpack.config.js in eine vite.config.js und die ausgelieferten Build-Artefakte sehen anders aus. Pinia statt Vuex: Die Core-Stores liegen unter Shopware.Store statt Shopware.State, und die alten Vuex-Helfer existieren nur umbenannt weiter — mapState heißt mapVuexState, mapMutations mapVuexMutations, mapGetters mapVuexGetters, mapActions mapVuexActions. Eigene Vuex-Stores lassen sich weiter anlegen; empfohlen ist es nicht. Dazu der Wechsel auf die Meteor Component Library, in dem aus sw-button ein mt-button wird und sich die v-model-Bindungen ändern. Für Sie heißt das: Ein 6.7-Release einer Erweiterung ist ein eigenes Paket mit eigenem Build, keine angepasste Versionsangabe.
Im PHP-Teil und in der Storefront sitzen die übrigen Kanten. Die Core-Klassen haben durchgehend native Typen an ihren Eigenschaften bekommen; jede Klasse, die davon erbt, muss die Typen nachziehen. Die Zahlungsabwicklung läuft über eine einheitliche AbstractPaymentHandler-Klasse, die fünf Interfaces ersetzt: AsyncPaymentHandlerInterface, SyncPaymentHandlerInterface, PreparedPaymentHandlerInterface, RefundPaymentHandlerInterface und RecurringPaymentHandlerInterface; ein Handler bekommt nur noch orderTransactionId, die Request-Daten, soweit sie anfallen, und den Context. Zahlungs- und Versandarten brauchen einen technicalName, verpflichtend in API und Datenbank — die Spalte technical_name wird in payment_method und shipping_method non-nullable. Custom-Field- und Fieldset-Namen dürfen keine Bindestriche und keine Punkte mehr enthalten, sie müssen gültige Twig-Variablennamen sein. Header und Footer laden per ESI über eigene Routen /header und /footer. Das Store-API-Route-Caching ist entfallen, mit ihm die Cached*Route-Klassen. Und für Plugins ist der Weg über eine Resources/config/entities.xml weg — Custom Entities per XML sind für Plugins entfernt; dort gehören jetzt eine eigene EntityDefinition oder attributbasierte Entitäten hin.
Wer eine Erweiterung für 6.6 und 6.7 gleichzeitig pflegt, pflegt zwei Linien: zwei Branches, zwei CI-Läufe, zwei Testmatrizen und im Store zwei Versionsstände unter einem Eintrag. Der Aufwand dafür entspricht eher einem Major-Sprung als einem Punkt-Release, und das ist kein Argument gegen 6.7, sondern eine Planungsgröße. Für den Bestand ist es seit Anfang 2026 deutlich entspannter: Die großen Store-Plugins haben kompatible Fassungen, anders als in den ersten Monaten nach dem Release. Wer nur einen eigenen Shop betreibt, braucht die zweite Linie ohnehin nicht — dann portieren wir einmal nach 6.7 und schließen 6.6 ab. Das Update-Projekt selbst, also Reihenfolge, Staging-Durchlauf und Rollback-Plan, steht auf einer eigenen Seite; hier geht es um den Code, der dabei mitziehen muss.
Verwaiste Plugins und die Übernahme fremden Codes
Wenn ein Hersteller nicht mehr liefert, gibt es drei Wege, und der erste Blick geht nicht in den Code, sondern in die Lizenz. Bei einer Mietlizenz aus dem Shopware Store endet das Nutzungsrecht mit der Mietzeit; ob Sie den Code danach forken, weiterentwickeln oder überhaupt weiterbetreiben dürfen, ist zuerst eine rechtliche und erst danach eine technische Frage. Das klärt Ihre Rechtsberatung, nicht Ihre Agentur. Steht die Erweiterung quelloffen auf Packagist oder GitHub, ist der Fork der kürzeste Weg: eigenes Repository, eigener Constraint, eigene Release-Verantwortung. Der zweite Weg ist Ersatz durch eine gepflegte Alternative, auch wenn dabei Konfiguration und Daten umgezogen werden müssen. Der dritte ist die Neuentwicklung — und die hat einen unterschätzten Vorteil: Die Spezifikation liegt vor, sie läuft seit Jahren in Ihrem Shop.
Vor einer Zusage zur Übernahme sehen wir uns denselben Satz Fragen an, jedes Mal. Was steht in der composer.json, insbesondere im Constraint gegen shopware/core und in den Abhängigkeiten? Gibt es eine reguläre Plugin-Basisklasse und einen sauberen Lebenszyklus, oder passiert die Installation in einer Migration? Was lässt die Deinstallation zurück? Wie viele Services werden dekoriert, und greift eine dieser Dekorationen auf Interna zu, die mit @internal markiert sind oder als privat gelten? Sind Storefront-Templates über sw_extends erweitert oder kopiert? Verwenden die überschriebenen Admin-Komponenten noch Vue-2-Muster oder Shopware.State? Existieren Tests, und laufen sie? Dazu ein Lauf von shopware-cli extension validate --full gegen die Ränder des Constraints und, wo es sich anbietet, extension format und extension fix. Das ist eine Arbeit von Stunden bis Tagen, nicht von Minuten, und sie ist ein abgegrenztes Erstprodukt mit eigenem Preis.
Am Ende steht ein Befundbericht: Portierungsaufwand nach Gewerken, Risikoliste mit Priorität und daneben die Alternative Neuentwicklung mit ihrer eigenen Zahl. Beide Zahlen stehen nebeneinander, auch wenn die Portierung der kleinere Auftrag ist. Und wir sagen ab, wenn ein Plugin so gebaut ist, dass wir für sein Verhalten nach dem nächsten Update nicht geradestehen können. Eine Erweiterung, die an fünf Stellen in undokumentierte Interna greift, ist kein Portierungsfall, sondern eine Neuentwicklung mit vorhandener Spezifikation — und dann ist es ehrlicher, das so zu nennen, als eine Portierung anzubieten, die beim nächsten Major-Release ein zweites Mal bezahlt werden muss. Eine Zusage ohne Blick in den Code wäre geraten, und Raten ist bei fremdem Code keine Methode.