eCommerce & SEO Magazin - eRock Marketing

Erweiterung von Shopware 6 Entities: Ein umfassender Leitfaden

Geschrieben von Steven Wimmer | 22.11.2025
1.  Der Unterschied zwischen Custom Fields und Entity Extensions
2.  Entities mit PHP Attributes erweitern (ab Shopware 6.6.3)
3.  Bestehende Entities durch Extensions erweitern
4.  Migration für neue Datenbankstrukturen
5.  Bulk Extensions für mehrere Entities
6.  API-Verfügbarkeit konfigurieren
7.  Mehrsprachigkeit bei Entity Extensions
8.  Praktische Tipps für die Entwicklung
9.  Daten schreiben und lesen
10.  Testen deiner Entity Extensions
11.  Fazit: Flexible Datenstrukturen für jeden Anwendungsfall




Es ist Montagmorgen und dein Kunde ruft an: „Wir brauchen für unsere Produkte zusätzliche technische Spezifikationen, die wir in verschiedenen Bereichen des Shops anzeigen können. Die Standard-Custom-Fields reichen nicht aus – wir brauchen komplexe Datenstrukturen mit Verknüpfungen." Ein typisches Szenario im E-Commerce-Alltag. Die gute Nachricht: Shopware Entities erweitern ist genau für solche Anforderungen konzipiert und bietet dir flexible Lösungen für nahezu jeden Anwendungsfall.

In diesem Artikel zeigen wir dir, wie du bestehende Shopware-Entitäten professionell erweiterst und dabei sowohl einfache Datenfelder als auch komplexe Assoziationen hinzufügst. Du lernst die verschiedenen Ansätze kennen und erfährst, wann welche Methode die richtige Wahl ist.

Der Unterschied zwischen Custom Fields und Entity Extensions

Bevor wir mit der praktischen Umsetzung beginnen, solltest du den grundlegenden Unterschied verstehen: Custom Fields sind perfekt für einfache, skalare Werte wie Textfelder oder Zahlen. Sie werden automatisch in der Administration verfügbar gemacht und in einem JSON-Feld gespeichert.

Entity Extensions hingegen bieten dir die volle Kontrolle über komplexe Datenstrukturen. Sie ermöglichen:

  • Verknüpfungen zwischen verschiedenen Entitäten
  • Spezielle Feldtypen und Validierungen
  • Performance-optimierte Datenbankstrukturen
  • Erweiterte Suchfunktionalitäten

Wenn deine Anforderungen über einfache Zusatzfelder hinausgehen, sind Entity Extensions der richtige Weg.

Entities mit PHP Attributes erweitern (ab Shopware 6.6.3)

Seit Shopware 6.6.3 kannst du Shopware Entities erweitern mit dem modernen PHP-Attribute-Ansatz. Diese Methode reduziert Boilerplate-Code erheblich und macht deine Definitionen übersichtlicher.

Eine neue Entity mit Attributes definieren

Hier siehst du, wie du eine vollständige Entity-Erweiterung mit dem Attribute-System erstellst:

<?php declare(strict_types=1);

namespace YourPlugin\Entity;

use Shopware\Core\Framework\DataAbstractionLayer\Entity;
use Shopware\Core\Framework\DataAbstractionLayer\Attribute\Entity as EntityAttribute;
use Shopware\Core\Framework\DataAbstractionLayer\Attribute\Field;
use Shopware\Core\Framework\DataAbstractionLayer\Attribute\FieldType;
use Shopware\Core\Framework\DataAbstractionLayer\Attribute\PrimaryKey;
use Shopware\Core\Framework\DataAbstractionLayer\Attribute\Required;
use Shopware\Core\Framework\DataAbstractionLayer\Attribute\ForeignKey;
use Shopware\Core\Framework\DataAbstractionLayer\Attribute\ManyToOne;

#[EntityAttribute('product_specification')]
class ProductSpecificationEntity extends Entity
{
    #[PrimaryKey]
    #[Field(type: FieldType::UUID)]
    public string $id;

    #[Required]
    #[Field(type: FieldType::STRING)]
    public string $specificationName;

    #[Field(type: FieldType::TEXT)]
    public ?string $specificationValue = null;

    #[Field(type: FieldType::INT)]
    public ?int $sortOrder = null;

    #[ForeignKey(entity: 'product')]
    public string $productId;

    #[ManyToOne(entity: 'product')]
    public ?ProductEntity $product = null;
}

Die Registrierung erfolgt dann einfach in deiner services.xml:

<service id="YourPlugin\Entity\ProductSpecificationEntity">
    <tag name="shopware.entity"/>
</service>

Vorteile der Attribute-Methode

Aspekt Traditionell Mit Attributes
Code-Zeilen ~150-200 ~50-80
Boilerplate Hoch Minimal
Typsicherheit Mittel Hoch
IDE-Support Standard Erweitert
Wartbarkeit Aufwendig Einfach


Bestehende Entities durch Extensions erweitern

Manchmal möchtest du keine neue Entity erstellen, sondern bestehende Shopware Entities erweitern. Hier zeigen wir dir beide Ansätze – mit und ohne Datenbankfelder.

Extension ohne neue Datenbanktabelle

Für berechnete Werte oder temporäre Daten kannst du Extensions nutzen, die keine eigenen Datenbankfelder benötigen:

<?php declare(strict_types=1);

namespace YourPlugin\Extension\Content\Product;

use Shopware\Core\Content\Product\ProductDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\EntityExtension;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;
use Shopware\Core\Framework\DataAbstractionLayer\Field\JsonField;

class ProductCalculatedExtension extends EntityExtension
{
    public function extendFields(FieldCollection $collection): void
    {
        $collection->add(
            new JsonField('calculated_data', 'calculatedData')
        );
    }

    public function getDefinitionClass(): string
    {
        return ProductDefinition::class;
    }
}

Diese Daten werden dann über Event-Subscriber zur Laufzeit hinzugefügt:

<?php declare(strict_types=1);

namespace YourPlugin\Subscriber;

use Shopware\Core\Content\Product\ProductEntity;
use Shopware\Core\Content\Product\ProductEvents;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityLoadedEvent;
use Shopware\Core\Framework\Struct\ArrayEntity;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class ProductExtensionSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            ProductEvents::PRODUCT_LOADED_EVENT => 'onProductsLoaded'
        ];
    }

    public function onProductsLoaded(EntityLoadedEvent $event): void
    {
        /** @var ProductEntity $product */
        foreach ($event->getEntities() as $product) {
            $calculatedData = [
                'pricePerUnit' => $this->calculatePricePerUnit($product),
                'stockStatus' => $this->determineStockStatus($product),
                'popularityScore' => $this->getPopularityScore($product)
            ];

            $product->addExtension('calculated_data',
                new ArrayEntity($calculatedData)
            );
        }
    }

    private function calculatePricePerUnit(ProductEntity $product): ?float
    {
        // Deine Berechnungslogik hier
        return null;
    }

    // Weitere Hilfsmethoden...
}

Extension mit eigener Datenbanktabelle

Für persistente Daten erstellst du eine OneToOne-Assoziation zu einer neuen Tabelle:

<?php declare(strict_types=1);

namespace YourPlugin\Extension\Content\Product;

use Shopware\Core\Content\Product\ProductDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\EntityExtension;
use Shopware\Core\Framework\DataAbstractionLayer\Field\OneToOneAssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;

class ProductSpecificationExtension extends EntityExtension
{
    public function extendFields(FieldCollection $collection): void
    {
        $collection->add(
            new OneToOneAssociationField(
                'productSpecification',
                'id',
                'product_id',
                ProductSpecificationDefinition::class,
                true // Automatisches Laden aktivieren
            )
        );
    }

    public function getDefinitionClass(): string
    {
        return ProductDefinition::class;
    }
}

Die zugehörige Definition sieht so aus:

<?php declare(strict_types=1);

namespace YourPlugin\Extension\Content\Product;

use Shopware\Core\Content\Product\ProductDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\Field\FkField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\PrimaryKey;
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Required;
use Shopware\Core\Framework\DataAbstractionLayer\Field\IdField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\JsonField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\OneToOneAssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\StringField;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;

class ProductSpecificationDefinition extends EntityDefinition
{
    public const ENTITY_NAME = 'your_plugin_product_specification';

    public function getEntityName(): string
    {
        return self::ENTITY_NAME;
    }

    public function getEntityClass(): string
    {
        return ProductSpecificationEntity::class;
    }

    protected function defineFields(): FieldCollection
    {
        return new FieldCollection([
            (new IdField('id', 'id'))->addFlags(new Required(), new PrimaryKey()),
            new FkField('product_id', 'productId', ProductDefinition::class),
            new StringField('material', 'material'),
            new StringField('dimensions', 'dimensions'),
            new JsonField('technical_specs', 'technicalSpecs'),
            new OneToOneAssociationField('product', 'product_id', 'id', ProductDefinition::class, false)
        ]);
    }
}

Migration für neue Datenbankstrukturen

Wenn du Shopware Entities erweitern möchtest und dabei neue Tabellen benötigst, erstellst du eine entsprechende Migration:

<?php declare(strict_types=1);

namespace YourPlugin\Migration;

use Doctrine\DBAL\Connection;
use Shopware\Core\Framework\Migration\MigrationStep;

class Migration1640995200ProductSpecification extends MigrationStep
{
    public function getCreationTimestamp(): int
    {
        return 1640995200;
    }

    public function update(Connection $connection): void
    {
        $sql = <<<SQL
CREATE TABLE IF NOT EXISTS `your_plugin_product_specification` (
    `id` BINARY(16) NOT NULL,
    `product_id` BINARY(16) NOT NULL,
    `product_version_id` BINARY(16) NOT NULL,
    `material` VARCHAR(255) NULL,
    `dimensions` VARCHAR(255) NULL,
    `technical_specs` JSON NULL,
    `created_at` DATETIME(3) NOT NULL,
    `updated_at` DATETIME(3) NULL,
    PRIMARY KEY (`id`),
    CONSTRAINT `unique.product_specification.product`
        UNIQUE (`product_id`, `product_version_id`),
    CONSTRAINT `fk.product_specification.product_id`
        FOREIGN KEY (`product_id`, `product_version_id`)
        REFERENCES `product` (`id`, `version_id`)
        ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
SQL;

        $connection->executeStatement($sql);
    }

    public function updateDestructive(Connection $connection): void
    {
        // Hier könntest du die Tabelle wieder entfernen
    }
}

Bulk Extensions für mehrere Entities

Wenn du mehrere Shopware Entities erweitern möchtest, bietet sich eine Bulk Extension an:

<?php declare(strict_types=1);

namespace YourPlugin\Extension;

use Shopware\Core\Content\Category\CategoryDefinition;
use Shopware\Core\Content\Product\ProductDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\BulkEntityExtension;
use Shopware\Core\Framework\DataAbstractionLayer\Field\FkField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\ManyToOneAssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\Field\StringField;

class MultiplEntityExtension extends BulkEntityExtension
{
    public function collect(): \Generator
    {
        yield ProductDefinition::ENTITY_NAME => [
            new StringField('seo_boost_factor', 'seoBoostFactor'),
            new FkField('main_category_id', 'mainCategoryId', CategoryDefinition::class)
        ];

        yield CategoryDefinition::ENTITY_NAME => [
            new StringField('marketing_label', 'marketingLabel'),
            new ManyToOneAssociationField('featuredProduct', 'featured_product_id', ProductDefinition::class)
        ];
    }
}

API-Verfügbarkeit konfigurieren

Bei modernen Entity-Erweiterungen steuerst du die API-Sichtbarkeit präzise über Attribute:

use Shopware\Core\Framework\Api\Context\AdminApiSource;
use Shopware\Core\Framework\Api\Context\SalesChannelApiSource;

#[EntityAttribute('product_specification')]
class ProductSpecificationEntity extends Entity
{
    // Überall verfügbar
    #[Field(type: FieldType::STRING, api: true)]
    public string $publicData;

    // Nur in der Admin-API
    #[Field(type: FieldType::STRING, api: [AdminApiSource::class])]
    public string $internalNotes;

    // Nur im Storefront
    #[Field(type: FieldType::STRING, api: [SalesChannelApiSource::class])]
    public string $customerVisibleData;

    // Nicht über API verfügbar
    #[Field(type: FieldType::STRING)]
    public string $internalData;
}

Mehrsprachigkeit bei Entity Extensions

Für mehrsprachige Datenfelder in deinen Entity-Erweiterungen verwendest du das translated-Attribut:

#[EntityAttribute('product_specification')]
class ProductSpecificationEntity extends Entity
{
    #[PrimaryKey]
    #[Field(type: FieldType::UUID)]
    public string $id;

    #[Required]
    #[Field(type: FieldType::STRING, translated: true)]
    public ?string $title = null;

    #[Field(type: FieldType::TEXT, translated: true)]
    public ?string $description = null;

    /**
     * @var array<string, ArrayEntity>|null
     */
    #[Translations]
    public ?array $translations = null;
}

Shopware erstellt automatisch die entsprechende Translation-Definition und -tabelle für dich.

Praktische Tipps für die Entwicklung

Checkliste für Entity Extensions

  • Repository-Pattern nutzen: Erstelle eigene Repository-Services für komplexe Abfragen
  • Caching berücksichtigen: Denke an bin/console cache:clear nach Änderungen an Definitionen
  • Plugin-Lifecycle: Nutze uninstall() und reinstall() beim Testen von Migrationen
  • Foreign Key Constraints: Setze immer entsprechende DB-Constraints für Datenintegrität
  • Performance: Verwende autoload: false bei Assoziationen, die nicht immer benötigt werden

Debugging von Entity Extensions

// Debug: Verfügbare Extensions anzeigen
public function debugExtensions(ProductEntity $product): array
{
    return array_keys($product->getExtensions());
}

// Debug: Repository-Services prüfen
bin/console debug:container | grep "your_entity_name"

// Debug: Routen für neue Entities
bin/console debug:router | grep "api"

Häufige Stolperfallen vermeiden

Problem: Extension wird nicht geladen

// services.xml prüfen
<service id="YourPlugin\Extension\ProductExtension">
    <tag name="shopware.entity.extension"/>
</service>

Problem: Migration-Fehler bei versionierten Entities

-- Vergiss nicht die Version-ID bei Product, Category etc.
`product_version_id` BINARY(16) NOT NULL,

Problem: API-Zugriff funktioniert nicht

// API-Flag explizit setzen
#[Field(type: FieldType::STRING, api: true)]
public string $apiField;

Daten schreiben und lesen

Das Schreiben in deine erweiterten Shopware Entities erfolgt über das Standard-Repository-Pattern:

// Service für Schreiboperationen
class ProductSpecificationService
{
    public function __construct(
        private EntityRepositoryInterface $productRepository
    ) {}

    public function addSpecification(string $productId, array $specData): void
    {
        $this->productRepository->update([
            [
                'id' => $productId,
                'productSpecification' => [
                    'material' => $specData['material'],
                    'dimensions' => $specData['dimensions'],
                    'technicalSpecs' => $specData['technical_specs']
                ]
            ]
        ], Context::createDefaultContext());
    }
}

Testen deiner Entity Extensions

<?php declare(strict_types=1);

namespace YourPlugin\Test\Integration;

use PHPUnit\Framework\TestCase;
use Shopware\Core\Framework\Test\TestCaseBase\IntegrationTestBehaviour;

class ProductSpecificationTest extends TestCase
{
    use IntegrationTestBehaviour;

    public function testProductSpecificationExtension(): void
    {
        $product = $this->createProduct();
       
        // Extension testen
        $this->assertInstanceOf(
            ProductSpecificationEntity::class,
            $product->getExtension('productSpecification')
        );
    }

    private function createProduct(): ProductEntity
    {
        // Deine Test-Implementierung
    }
}

Fazit: Flexible Datenstrukturen für jeden Anwendungsfall

Shopware Entities erweitern bietet dir mächtige Werkzeuge für maßgeschneiderte E-Commerce-Lösungen. Mit dem modernen Attribute-System reduzierst du Boilerplate-Code erheblich, während traditionelle Extensions dir maximale Kontrolle über komplexe Datenstrukturen geben.

Die Wahl der richtigen Methode hängt von deinen spezifischen Anforderungen ab:

  • Custom Fields für einfache, administrierbare Zusatzdaten
  • Runtime Extensions für berechnete oder temporäre Werte
  • Persistente Extensions für komplexe, verknüpfte Datenstrukturen
  • Bulk Extensions für übergreifende Erweiterungen mehrerer Entities

Mit den gezeigten Patterns und der systematischen Herangehensweise meisterst du auch komplexeste Erweiterungsszenarien. Deine Kunden erhalten genau die Datenstrukturen, die ihr Business benötigt – performant, wartbar und zukunftssicher implementiert.