---
title: Extending Existing Entities | Shopware Community Hub
description: >-
  Learn how to extend existing Shopware entities using entity extensions and
  associations.
canonical_url: 'https://hub.shopware.com/learn/unit/extend-existing-entities'
---

# Extending Existing Entities

<LearningObjectives>

- Understand how to extend existing Shopware entities with additional fields and associations.
- Learn how to create entity extensions that add new functionality to core entities.
- Understand the role of the EntityExtension class and how it integrates with existing entity definitions.
- Implement proper service registration for entity extensions so that they are discovered by Shopware.
- Use entity extensions to define relationships between custom and core entities.
- Apply best practices for extending entities while keeping performance and maintainability in mind.

</LearningObjectives>

# Extending Existing Entities

So far, we've learned how to create our own custom entities from scratch. But what if you want to add new fields or relationships to entities that already exist in Shopware? This is where entity extensions come into play, and it's a powerful concept that allows you to enhance core Shopware functionality without modifying the original code.

In this learning unit, we'll explore how to extend existing entities using Shopware's EntityExtension system.

We'll use our [AcademyProductNotes plugin](https://github.com/ShopwareAcademy/AcademyProductNotes) as a practical example, where we extend the core `Product` entity to add a relationship to our custom `ProductNote` entities.

## Understanding Entity Extensions

Entity extensions are a fundamental concept in Shopware that allows you to add new fields, associations, and functionality to existing entities without touching the core code. This approach provides several benefits:

- **Non-destructive**: Entity extensions do not modify existing core fields or behavior.
- **Upgrade-compatible**: Entity extensions are designed to remain compatible with future Shopware updates.
- **Modular**: Multiple plugins can extend the same entity independently.
- **Flexible**: You can add almost any type of field or association.
- **Maintainable**: Clear separation between core and custom functionality.

Think of entity extensions as a way to "inject" new capabilities into existing entities. It's like adding new features to a car without taking the engine apart - you're enhancing what's already there rather than rebuilding it from scratch.

<Callout title="Note" type="info">

Shopware also supports **Bulk Entity Extensions**, which allow you to group and register multiple entity extensions together. This is useful when you need to extend multiple core entities within a single extension setup.

</Callout>

## Why Extend the Product Entity?

In our AcademyProductNotes plugin, we want to create a relationship between products and product notes. This means that when someone looks at a product, they should be able to see all the notes associated with it.

The most logical way to achieve this is to extend the existing `Product` entity to include a relationship to our `ProductNote` entities. This way, we can:

- Query products and get their associated notes in a single request
- Display notes directly in the product detail pages
- Maintain referential integrity between products and notes
- Leverage Shopware's existing product management infrastructure

## Step 1: Creating the Entity Extension Class

Let's look at how we create an entity extension. The process involves creating a class that extends `EntityExtension` and implementing the required methods.

### The ProductExtension Class

```php
<?php declare(strict_types=1);

namespace AcademyProductNotes\Core\Content\Product;

use AcademyProductNotes\Core\Content\ProductNote\ProductNoteDefinition;
use Shopware\Core\Content\Product\ProductDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\EntityExtension;
use Shopware\Core\Framework\DataAbstractionLayer\Field\OneToManyAssociationField;
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;

class ProductExtension extends EntityExtension
{
    public const string EXTENSION_NAME = 'academyProductNotes';

    public function extendFields(FieldCollection $collection): void
    {
        $collection->add(
            new OneToManyAssociationField(
                self::EXTENSION_NAME,
                ProductNoteDefinition::class,
                'product_id'
            )
        );
    }

    public function getEntityName(): string
    {
        return ProductDefinition::ENTITY_NAME; // 'product'
    }
}
```

Let me break down what's happening in this class:

### Understanding the EntityExtension Class

The `EntityExtension` class is an abstract class that provides the framework for extending existing entities. When you extend it, you must implement two key methods:

1. **`extendFields(FieldCollection $collection)`**: This is where you add your new fields and associations to the existing entity
2. **`getEntityName()`**: This provides the name of the entity being extended (e.g. `product`).

<Callout title="Version Note" type="info">

Since Shopware 6.6.10, `EntityExtension::getDefinitionClass()` is deprecated. In Shopware 6.7 you should implement `getEntityName()` and return the entity name (e.g. `product`).

</Callout>

### The extendFields Method

The `extendFields` method is the heart of your entity extension. It receives a `FieldCollection` object that contains all the existing fields of the entity you're extending. You can then add new fields to this collection.

In our case, we're adding a `OneToManyAssociationField` that creates a relationship between products and product notes. Let's understand the parameters:

- **`academyProductNotes`**: This is the name of the association field. It will be available as a property on Product entities.
- **`ProductNoteDefinition::class`**: This tells Shopware which entity definition this association points to.
- **`product_id`**: This is the foreign key field in the ProductNote entity that references the product. Or in other words, this is the **database column name** in the `academy_product_note` table that references the product.

### Understanding OneToManyAssociationField

A `OneToManyAssociationField` represents a relationship where **one entity** (in this case, a Product) **can have many related entities** (ProductNotes). This is the perfect choice for our use case because:

- One product can have multiple notes
- Each note belongs to exactly one product
- We want to be able to access all notes for a product easily

## Step 2: Understanding the Relationship Structure

Let's visualize how this extension creates a relationship between products and product notes:

```txt
Product Entity (existing)
├── id
├── name
├── description
├── price
└── ... (other existing fields)
    └── academyProductNotes (our new association)
        └── Points to ProductNote entities where product_id matches

ProductNote Entity (our custom entity)
├── id
├── product_id (foreign key to Product)
├── product_version_id (foreign key to Product version)
├── note
├── solved
└── ... (other existing fields)
```

When you extend the Product entity with this association, you can now:

- Access the notes via the entity extension (see `getExtensionOfType(...)` below)
- Use the association in queries to load products with their notes
- Maintain referential integrity between products and notes

## Step 3: Registering the Entity Extension

Creating the extension class is only half the work. You also need to register it with Shopware's service container so that it knows about your extension.

<Callout title="Important" type="info">

Shopware only applies entity extensions that are registered as services with the `shopware.entity.extension` tag. Otherwise, Shopware won't discover the extension and the additional fields/associations won't be available.

</Callout>

### Service Registration

```xml
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <!-- Entity Definition -->
        <service id="AcademyProductNotes\Core\Content\ProductNote\ProductNoteDefinition">
            <tag name="shopware.entity.definition" />
        </service>

        <!-- Product Extension -->
        <service id="AcademyProductNotes\Core\Content\Product\ProductExtension">
            <tag name="shopware.entity.extension" />
        </service>
    </services>
</container>
```

The key part here is the `<tag name="shopware.entity.extension" />` tag. This tells Shopware that this service is an entity extension and should be processed during the system initialization.

### How Service Registration Works

When Shopware starts up, it:

1. Scans all service definitions for the `shopware.entity.extension` tag
2. Instantiates each extension service
3. Calls the `getEntityName()` method to determine which entity to extend
4. Calls the `extendFields()` method to add new fields to the existing entity definition
5. Merges the extended fields with the original entity definition

This process happens automatically, so you don't need to manually wire anything together.

## Step 4: Using the Extended Entity

Now that we've extended the Product entity, let's see how we can use this new association in practice.

### Loading Products With Notes

```php
public function getProductWithNotes(string $productId, Context $context): ?ProductEntity
{
    $criteria = new Criteria([$productId]);

    // Load the academyProductNotes association/extension
    $criteria->addAssociation(ProductExtension::EXTENSION_NAME);

    /** @var ProductEntity|null $product */
    $product = $this->productRepository->search($criteria, $context)->first();

    return $product;
}
```

### Accessing Notes From a Product

After adding the association via the `Criteria`, you can access the notes on the loaded `ProductEntity`:

```php
$criteria = new Criteria([$productId]);
$criteria->addAssociation(ProductExtension::EXTENSION_NAME);

/** @var ProductEntity|null $product */
$product = $this->productRepository->search($criteria, $context)->first();

/** @var ProductNoteCollection|null $notes */
$notes = $product?->getExtensionOfType(ProductExtension::EXTENSION_NAME, ProductNoteCollection::class);

if (null === $notes) {
    return;
}

$notesData = [];
foreach ($notes as $note) {
    $notesData[] = [
        'id' => $note->getId(),
        'note' => $note->getNote(),
        // ...
    ];
}

return $notesData;
```

### Filtering Products by Notes

You can also use the association in reverse to find products that have specific notes:

```php
public function findProductsWithNotesContaining(string $searchTerm, Context $context): EntitySearchResult
{
    $criteria = new Criteria();

    // Load the notes association
    $criteria->addAssociation(ProductExtension::EXTENSION_NAME);

    // Filter products that have notes containing the search term
    $criteria->addFilter(
        new ContainsFilter(
            ProductExtension::EXTENSION_NAME . '.note', // "academyProductNotes.note"
            $searchTerm
        )
    );

    return $this->productRepository->search($criteria, $context);
}
```

## Step 5: Extending Products With Associations

While our current extension is relatively simple, entity extensions can also be used to attach additional structured data to existing entities.

### Making Product Notes Available via the Product DAL

`extendFields()` receives the `FieldCollection` of the entity you are extending (here: `Product`). It allows you to add new fields to the **DAL definition** of that entity.

In our case, we are not adding new columns to the `product` table. Instead, we persist product notes in our own table (`academy_product_note`) and link them to products via `product_id` and `product_version_id`.

The purpose of the `OneToManyAssociationField` below is to make these notes available through the `product`'s DAL representation. This means:

- It enables loading `academyProductNotes` with a product query via `Criteria::addAssociation(ProductExtension::EXTENSION_NAME)` (respectively: `Criteria::addAssociation('academyProductNotes')`).
- It makes the notes accessible on the loaded `ProductEntity` via the extension name.

Without this association field, the data would still exist in `academy_product_note`, but you would have to query it separately via the product note repository and join it manually in your own code.

Depending on your use case, this can be perfectly sufficient: If you only need product notes when working with `ProductNote` data (and never need to navigate from `Product` to its notes), you can skip extending the `Product` entity.

In that case, you simply query the `academy_product_note.repository` by `productId` (and `productVersionId`).

The following example illustrates how such an extension could look:

```php
public function extendFields(FieldCollection $collection): void
{    
    // Add our existing association
    $collection->add(
        new OneToManyAssociationField(
            self::EXTENSION_NAME,
            ProductNoteDefinition::class,
            'product_id'
        )
    );
}
```

### Entity Extensions vs. Custom Fields

This example is **not** a Shopware **custom field** (stored in the `custom_fields` JSON column).

If you want to dive deeper about the different approaches for extending entities, refer to the official Shopware documentation:

- [Adding Complex Data to Existing Entities](https://developer.shopware.com/docs/guides/plugins/plugins/framework/data-handling/add-complex-data-to-existing-entities.html)
- [Add Custom Field](https://developer.shopware.com/docs/guides/plugins/plugins/framework/custom-field/add-custom-field.html)

If you would like a refresher specifically on creating custom fields, revisit the [dedicated learning unit](/learn/unit/add-data-to-your-product) from the previous learning path.

### Multiple Extensions

Multiple plugins can extend the same entity, and they'll all work together:

```php
// Plugin A extends Product with fieldA
// Plugin B extends Product with fieldB
// Plugin C extends Product with fieldC

// Result: Product entity has fieldA, fieldB, fieldC, and academyProductNotes
```

## Step 6: Best Practices and Considerations

As you work with entity extensions, keep these important principles in mind:

### Naming Conventions

- **Use descriptive names**: `academyProductNotes` is better than just `notes`
- **Avoid conflicts**: Use your plugin prefix to avoid naming collisions
- **Be consistent**: Follow the same naming pattern across your plugin

### Performance Considerations

- **Explicit loading**: Associations are loaded only when they are explicitly defined via criteria.
- **Selective loading**: Only load associations that are required for your specific use case.
- **Efficient queries**: Carefully defining associations helps avoid unnecessary joins and improve query performance.

### Maintenance and Updates

- **Test thoroughly**: Entity Extensions can influence queries and data loading behavior.
- **Document your extensions**: Clearly describe which fields or associations are added.
- **Consider backward compatibility**: Don't remove fields without a good reason.

## Practical Example: Complete Product Notes Integration

Let's see how all this comes together in a real-world scenario:

```php
public function getProductNotesDashboard(string $productId, Context $context): array
{
    $criteria = new Criteria([$productId]);
    $criteria->addAssociation(ProductExtension::EXTENSION_NAME);

    /** @var ProductEntity|null $product */
    $product = $this->productRepository->search($criteria, $context)->first();
    if (null === $product) {
        throw new InvalidArgumentException(sprintf('Product "%s" not found', $productId));
    }

    /** @var ProductNoteCollection|null $notes */
    $notes = $product->getExtensionOfType(ProductExtension::EXTENSION_NAME, ProductNoteCollection::class);

    $notesData = [];
    if (null !== $notes) {
        /** @var ProductNoteEntity $note */
        foreach ($notes as $note) {
            $notesData[] = [
                'id' => $note->getId(),
                'note' => $note->getNote(),
                'createdAt' => $note->getCreatedAt()?->format(self::DATE_FORMAT),
                'solved' => $note->isSolved(),
            ];
        }
    }

    return [
        'product' => [
            'id' => $product->getId(),
            'name' => $product->getName(),
            'noteCount' => $notes?->count() ?? 0,
        ],
        'notes' => $notesData,
    ];
}
```

This method demonstrates the full power of entity extensions:

1. **Loads the product** with all its associated notes.
2. **Accesses the extended association** using `getExtensionOfType(...)`.
3. **Iterates over the related entities** to prepare response data.
4. **Returns a structured result** containing product and product-note information.

<Callout title="Explore the Complete Example" type="info">

Feel free to explore the complete example in the [AcademyProductNotes plugin repository](https://github.com/ShopwareAcademy/AcademyProductNotes).

You can also clone the repository and explore the code yourself:

```bash
# In your custom/plugins folder
git clone git@github.com:ShopwareAcademy/AcademyProductNotes.git
```

</Callout>

## Summary

Entity extensions are a powerful way to enhance existing Shopware entities without modifying core code. Here's what we've learned:

1. **Entity extensions are non-destructive**. They add functionality without breaking existing core behavior.
2. **The EntityExtension class** provides the foundation for extending entities.
3. **Service registration** is essential for Shopware to recognize your entity extensions.
4. **Associations** allow you to create relationships between custom and core entities.
5. **Extended entities work seamlessly** with existing Shopware functionality.
6. **Multiple extensions can coexist** on the same entity without interfering with each other.
7. **Performance and maintenance** considerations are important for production use.

Congratulations! You have successfully completed this learning unit and finished the first course of this learning path!
