Finding Extension Points
Overview
Shopware 6 provides a modern extension system that allows you to intercept and modify core functionality. Unlike traditional events that are primarily for notifications, Extension Points are designed for replacing and extending core system processes.
This guide will cover how you can find available Extension Points in the Shopware codebase to use them in your plugin.
Extension Classes
Extension Points in Shopware extend the base Extension
class and are typically located in domain-specific directories. They follow a consistent naming pattern and structure.
Finding Extension Classes
You can find Extension classes by searching for the following patterns in the Shopware source code:
Search Terms
extends Extension
: Find all Extension classesExtension<
: Find typed Extension Points with specific return typesExtensionDispatcher
: Find where Extension Points are dispatched
Common Locations
Extension Points are typically located in:
src/Core/Content/*/Extension/
src/Core/Checkout/*/Extension/
src/Core/Content/Cms/Extension/
src/Core/Content/Product/Extension/
Example Extension Classes
Here are some common Extension Point you might encounter:
Product Extensions
// Product price calculation
src/Core/Content/Product/Extension/ProductPriceCalculationExtension.php
// Product listing resolution
src/Core/Content/Product/Extension/ResolveListingExtension.php
// Product listing criteria modification
src/Core/Content/Product/Extension/ProductListingCriteriaExtension.php
Cart Extensions
// Checkout place order
src/Core/Checkout/Cart/Extension/CheckoutPlaceOrderExtension.php
// Cart rule loading
src/Core/Checkout/Cart/Extension/CheckoutCartRuleLoaderExtension.php
CMS Extensions
// CMS slots data enrichment
src/Core/Content/Cms/Extension/CmsSlotsDataEnrichExtension.php
// CMS slots data resolution
src/Core/Content/Cms/Extension/CmsSlotsDataResolveExtension.php
Extension Naming Convention
Extension Points follow a consistent naming pattern:
Event Names
Extension Points use a NAME
constant that defines the event name:
final class ResolveListingExtension extends Extension
{
public const NAME = 'listing-loader.resolve';
// ...
}
Event Lifecycle
Extension Points are dispatched with lifecycle suffixes:
{name}.pre
- Before the default implementation{name}.post
- After the default implementation{name}.error
- When an error occurs
Finding Extension Usage
In Service Definitions
Services that use Extension Points typically inject the ExtensionDispatcher
:
<service id="Some\Service">
<argument type="service" id="Shopware\Core\Framework\Extensions\ExtensionDispatcher"/>
</service>
In Constructor Parameters
Look for services that inject the ExtensionDispatcher
:
public function __construct(
private readonly ExtensionDispatcher $extensionDispatcher
) {
}
Extension Dispatch Pattern
Extension Points are typically dispatched using this pattern:
$extension = new SomeExtension($parameters);
$result = $this->extensionDispatcher->publish(
SomeExtension::NAME,
$extension,
function() use ($parameters) {
// Default implementation
return $this->defaultImplementation($parameters);
}
);
Common Extension Types
Product Extensions
ProductPriceCalculationExtension
Purpose: Intercept and modify product price calculations Event Name: product.calculate-prices
Return Type: void
final class ProductPriceCalculationExtension extends Extension
{
public const NAME = 'product.calculate-prices';
public function __construct(
public readonly iterable $products,
public readonly SalesChannelContext $context
) {}
}
ResolveListingExtension
Purpose: Replace product listing resolution logic Event Name: listing-loader.resolve
Return Type: EntitySearchResult<ProductCollection>
final class ResolveListingExtension extends Extension
{
public const NAME = 'listing-loader.resolve';
public function __construct(
public readonly Criteria $criteria,
public readonly SalesChannelContext $context
) {}
}
Cart Extensions
CheckoutPlaceOrderExtension
Purpose: Intercept order placement process Event Name: checkout.place-order
Return Type: OrderPlaceResult
final class CheckoutPlaceOrderExtension extends Extension
{
public const NAME = 'checkout.place-order';
public function __construct(
public readonly Cart $cart,
public readonly SalesChannelContext $context
) {}
}
CMS Extensions
CmsSlotsDataEnrichExtension
Purpose: Enrich CMS slot data before rendering Event Name: cms.slots.data-enrich
Return Type: CmsSlotCollection
final class CmsSlotsDataEnrichExtension extends Extension
{
public const NAME = 'cms.slots.data-enrich';
public function __construct(
public readonly CmsSlotCollection $slots,
public readonly SalesChannelContext $context
) {}
}
Using Extensions in Your Plugin
Event Subscriber
Create an event subscriber to listen for Extension Points:
<?php declare(strict_types=1);
namespace MyPlugin\Subscriber;
use Shopware\Core\Content\Product\Extension\ResolveListingExtension;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class ProductListingSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
'listing-loader.resolve.pre' => 'onResolveListing',
];
}
public function onResolveListing(ResolveListingExtension $event): void
{
// Custom logic here
$event->result = $this->customProductLoader->load($event->criteria, $event->context);
$event->stopPropagation();
}
}
Service Registration
Register your subscriber in the service configuration:
<service id="MyPlugin\Subscriber\ProductListingSubscriber">
<tag name="kernel.event_subscriber"/>
</service>
Extension Lifecycle
Extension Points follow a specific lifecycle:
- Pre-Event:
{name}.pre
- Before default implementation - Default Implementation: Core logic (if not stopped)
- Post-Event:
{name}.post
- After implementation - Error-Event:
{name}.error
- If an error occurs
Lifecycle Example
public function handleExtension(SomeExtension $event): void
{
// This runs in the .pre phase
if ($this->shouldReplaceDefault($event)) {
$event->result = $this->customImplementation($event);
$event->stopPropagation(); // Prevents default implementation
}
}
public function handlePostExtension(SomeExtension $event): void
{
// This runs in the .post phase
$this->logger->info('Extension completed', ['result' => $event->result]);
}
Best Practices
1. Use Type Hints
Always use proper type hints for Extension Point parameters:
public function onResolveListing(ResolveListingExtension $event): void
{
// Type-safe access to properties
$criteria = $event->criteria;
$context = $event->context;
}
2. Handle Results Properly
Check if a result has already been set:
public function onExtension(SomeExtension $event): void
{
if ($event->result !== null) {
// Another extension already provided a result
return;
}
$event->result = $this->myImplementation($event);
}
3. Use Stop Propagation Wisely
Only stop propagation when you're providing a complete replacement:
public function onExtension(SomeExtension $event): void
{
if ($this->shouldReplaceDefault($event)) {
$event->result = $this->completeReplacement($event);
$event->stopPropagation();
}
// If not stopped, default behavior continues
}
4. Error Handling
Extension Points have built-in error handling, but you can also handle errors gracefully:
public function onExtension(SomeExtension $event): void
{
try {
$event->result = $this->riskyOperation($event);
} catch (\Exception $e) {
// Log the error but don't stop the extension
$this->logger->error('Extension failed', ['error' => $e->getMessage()]);
// Let the extension system handle the error
}
}
Debugging Extensions
Using the Symfony Profiler
The Symfony profiler shows all dispatched Extension Points in the "Events" tab. Look for events with .pre
, .post
, or .error
suffixes.
Logging Extension Calls
You can log Extension Point calls to understand the flow:
public function onExtension(SomeExtension $event): void
{
$this->logger->debug('Extension called', [
'extension' => get_class($event),
'hasResult' => $event->result !== null,
'stopped' => $event->isPropagationStopped()
]);
}
This comprehensive guide should help you find and use Extension Points effectively in your Shopware plugins.