Seit Shopware 6.4 gibt es offiziell zwei Wege, den Shop zu erweitern: das klassische Plugin und das App System. Beide können Funktionen hinzufügen, Daten verwalten und Prozesse automatisieren — aber sie tun es auf fundamental unterschiedliche Weise. Wer ein Projekt plant, muss früh entscheiden, welchen Weg er geht, denn die Architekturen sind nicht kompatibel.
Das Plugin-Modell
Plugins sind PHP-Pakete, die direkt in den Shopware-Prozess geladen werden. Sie haben Zugriff auf alle Symfony-Dienste, den Dependency Injection Container, die Datenbank und alle Shopware-interna.
Typische Plugin-Struktur
MyPlugin/
├── composer.json
├── src/
│ ├── MyPlugin.php # Plugin-Klasse
│ ├── Core/
│ │ └── Content/ # Custom Entities, DAL-Erweiterungen
│ ├── Service/ # Business Logic
│ └── Resources/
│ ├── config/
│ │ └── services.xml # Service-Definitionen
│ └── views/ # Twig-Templates
└── Migration/ # Datenbank-Migrationen
Plugin-Klasse
<?php
declare(strict_types=1);
namespace MyPlugin;
use Shopware\Core\Framework\Plugin;
use Shopware\Core\Framework\Plugin\Context\InstallContext;
use Shopware\Core\Framework\Plugin\Context\UninstallContext;
class MyPlugin extends Plugin
{
public function install(InstallContext $installContext): void
{
// Einmalige Initialisierung beim Installieren
}
public function uninstall(UninstallContext $uninstallContext): void
{
if ($uninstallContext->keepUserData()) {
return;
}
// Datenbank-Cleanup
}
}
Was Plugins können
- Eigene Entities und DAL-Definitionen anlegen
- Bestehende Entities mit eigenen Feldern erweitern
- Symfony-Dienste registrieren und überschreiben
- Event-Subscriber für alle Shopware-Events
- Eigene Admin-Komponenten (Vue.js) ins Backend einbinden
- Eigene Storefront-Templates mit Template-Inheritance
- Direkte Datenbankoperationen mit vollem Zugriff
Plugins laufen im gleichen PHP-Prozess wie Shopware. Das bedeutet: direkter Zugriff auf alle Ressourcen, aber auch das Risiko, den gesamten Shop durch einen Fehler lahmzulegen.
Das App System
Das App System wurde eingeführt, um Erweiterungen zu ermöglichen, die außerhalb des Shopware-Prozesses laufen. Eine App ist ein externer Service, der über Webhooks und die Admin-API kommuniziert.
Typische App-Struktur
MyApp/
├── manifest.xml # App-Manifest (kein PHP!)
├── Resources/
│ ├── views/ # Twig-Templates (für Storefront-Blöcke)
│ └── app/
│ └── administration/ # Admin-Erweiterungen
└── (externer Webserver) # Dein eigener Service
manifest.xml
Die zentrale Datei einer App definiert alles deklarativ:
<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/shopware/trunk/src/Core/Framework/App/Manifest/Schema/manifest-2.0.xsd">
<meta>
<name>MyApp</name>
<label>Meine App</label>
<description>Eine Demo-App</description>
<author>Wunner Software</author>
<copyright>(c) 2025 Wunner Software</copyright>
<version>1.0.0</version>
<license>proprietary</license>
</meta>
<permissions>
<read>order</read>
<read>customer</read>
<create>order_tag</create>
</permissions>
<webhooks>
<webhook name="order-placed"
url="https://my-app.example.com/webhook/order-placed"
event="checkout.order.placed"/>
</webhooks>
<setup>
<registrationUrl>https://my-app.example.com/register</registrationUrl>
<!-- Hardcoded secrets are for local development only.
In production/Store apps, the secret is exchanged automatically during the registration handshake. -->
<secret>my-secret-for-hmac-validation</secret>
</setup>
</manifest>
Webhook-Handler (externer Service, z.B. Symfony)
<?php
declare(strict_types=1);
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class WebhookController extends AbstractController
{
#[Route('/webhook/order-placed', methods: ['POST'])]
public function orderPlaced(Request $request): Response
{
// HMAC-Signatur prüfen
// shopware-shop-signature is the correct header for incoming webhooks.
// (shopware-app-signature is used for responses FROM the app, not incoming requests.)
$signature = $request->headers->get('shopware-shop-signature');
$body = $request->getContent();
if (!$this->validateSignature($signature, $body)) {
return new Response('Unauthorized', Response::HTTP_UNAUTHORIZED);
}
$data = json_decode($body, true);
$orderId = $data['data']['payload']['id'] ?? null;
if ($orderId !== null) {
// Eigene Business-Logik: z.B. ERP-Integration
$this->erpService->syncOrder($orderId);
}
return new Response('', Response::HTTP_OK);
}
private function validateSignature(?string $signature, string $body): bool
{
if ($signature === null) {
return false;
}
$expected = hash_hmac('sha256', $body, 'my-secret-for-hmac-validation');
return hash_equals($expected, $signature);
}
}
Admin-API-Aufrufe aus der App
Die App erhält beim Registrierungsprozess einen API-Key und kann dann die Shopware-Admin-API nutzen:
<?php
declare(strict_types=1);
namespace App\Service;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class ShopwareApiClient
{
public function __construct(
private readonly HttpClientInterface $httpClient,
private readonly string $shopUrl,
private readonly string $accessToken,
) {
}
public function getOrder(string $orderId): array
{
$response = $this->httpClient->request('GET', sprintf('%s/api/order/%s', $this->shopUrl, $orderId), [
'headers' => [
'Authorization' => 'Bearer ' . $this->accessToken,
'Content-Type' => 'application/json',
],
]);
return $response->toArray();
}
public function addTagToOrder(string $orderId, string $tagId): void
{
$this->httpClient->request('POST', sprintf('%s/api/order/%s/tags', $this->shopUrl, $orderId), [
'headers' => [
'Authorization' => 'Bearer ' . $this->accessToken,
'Content-Type' => 'application/json',
],
'json' => [['id' => $tagId]],
]);
}
}
Direkter Vergleich
| Kriterium | Plugin | App |
|---|---|---|
| Laufzeitumgebung | Im Shopware-Prozess | Externer Service |
| Programmiersprache | PHP (erzwungen) | Beliebig (PHP, Node.js, Python...) |
| Datenbankzugriff | Direkt (DAL/SQL) | Nur via Admin-API |
| Performance-Einfluss | Direkt auf Shop | Asynchron via Webhooks |
| Deployment | Auf dem Shop-Server | Eigener Server/Cloud |
| Shopware Cloud kompatibel | Nein | Ja |
| Template-Overrides | Vollständig | Begrenzt (Blocks) |
| Admin-Erweiterungen | Vollständige Vue.js-Komponenten | Begrenzt (iFrame, Module) |
| Fehlerrisiko für Shop | Hoch (gleicher Prozess) | Niedrig (isoliert) |
| Entwicklungskomplexität | Mittel-hoch | Mittel (mehr Infrastruktur) |
Wann Plugin, wann App?
Plugin ist die richtige Wahl, wenn...
- tiefer Eingriff in Shopware-Core-Logik nötig ist (z.B. Preisberechnung, Checkout-Flow)
- eigene DAL-Entities benötigt werden, die Shopware direkt verwalten soll
- der Shop auf einem selbstverwalteten Server läuft
- umfangreiche Storefront-Anpassungen (Template-Inheritance) gemacht werden
- kein externer Server betrieben werden soll
App ist die richtige Wahl, wenn...
- der Shop auf Shopware Cloud oder bei einem Hoster läuft, der keine Plugins erlaubt
- die Erweiterung mit externen Systemen kommuniziert (ERP, CRM, Warenwirtschaft)
- die Logik in einer anderen Sprache oder einem bestehenden Service läuft
- Isolation und Ausfallsicherheit wichtig sind (ein App-Fehler bremst den Shop nicht)
- das Produkt als SaaS an mehrere Shop-Betreiber verkauft werden soll
Hybride Ansätze
In der Praxis kombiniert man oft beide Modelle:
- Ein leichtes Plugin registriert Custom Fields oder Flow-Actions
- Der eigentliche Business-Service läuft als App auf eigenem Server
- Kommunikation läuft über die Admin-API und Webhooks
Das ist besonders bei komplexen B2B-Integrationsprojekten sinnvoll, bei denen Shopware als Frontend und Bestellsystem dient, aber ERP und Lager extern verwaltet werden.
Fazit
Das Plugin-Modell bleibt für lokale Shopware-Installationen die mächtigere Option, weil es direkten Zugriff auf alle Shopware-interna hat. Das App System ist flexibler in der Wahl der Technologie, isolierter und cloud-kompatibel — aber auf die Admin-API beschränkt. Wer neue Projekte plant, sollte ernsthaft das App System evaluieren, besonders wenn eine externe Service-Architektur ohnehin angedacht ist.
Kommentare
Kommentare werden von Remark42 bereitgestellt. Beim Laden werden Daten an unseren Kommentar-Server übertragen.