Shopware 6.7 marks a significant maturity milestone for the platform’s plugin ecosystem. With stricter dependency injection rules, deeper integration with Symfony Messenger, and refined manifest validation, building extensions has become more robust—but also less forgiving of outdated patterns. Many developers migrate from older Shopware versions or jump straight into 6.7 without adjusting their mental model, leading to plugins that work locally but fail in production or break during future updates. Below are the most frequent mistakes plugin creators make when targeting Shopware 6.7 and beyond, along with practical guidance to avoid them.
1. Misconfiguring Dependency Injection & Service Definitions
Shopware 6.7 enforces modern Symfony DI practices. A common pitfall is manually wiring services in services.xml without leveraging autowiring or proper namespace alignment. Developers often forget that Shopware now validates service IDs against a strict naming convention: namespace.to.ClassName. If your service ID doesn’t match the fully qualified class name, the container will either fail to compile or inject unexpected defaults.
<!-- shopware-platform-api/CustomPlugin/src/Resources/services.xml -->
<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
https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="CustomPlugin\Service\CartProcessor" public="true">
<argument type="service" id="cart.transaction.service"/>
<tag name="shopware.cart.calculator"/>
</service>
</services>
</container>
Notice the id matches the class path exactly, and dependencies are injected via constructor. In 6.7, relying on service aliases or older @ syntax triggers deprecation warnings and may cause runtime failures. Always prefer constructor injection and let the container resolve shared vs. singleton scopes automatically.
2. Ignoring the Command Bus & Event Dispatching Shifts
Shopware 6.7 fully embraces the Messenger component for background processing. Old plugins often bypass the bus or dispatch events directly using EventDispatcherInterface, which defeats async capabilities and causes performance bottlenecks. When you trigger logic that could run synchronously (like sending confirmation emails or updating search indices), wrap it in a Command class and route it through the appropriate bus.
// src/Command/ProductIndexingCommand.php
namespace CustomPlugin\Command;
use Shopware\Core\Framework\MessageQueue\ScheduledTask\AbstractScheduledTaskHandler;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler]
class ProductIndexingCommand extends AbstractScheduledTaskHandler
{
public function handle(): void
{
// Heavy work runs asynchronously without blocking requests
}
}
Pair this with a proper scheduled_tasks.xml definition, and ensure your task handler declares the correct bus tag (shopware.messenger.handler). Direct event dispatching should be reserved for short-lived, synchronous business logic. Mixing sync/async patterns unpredictably leads to race conditions and data inconsistency in 6.7’s more transactional architecture.
3. Bypassing Entity Repositories with Raw SQL
Direct database connections via DatabaseConnection::query() are deprecated and actively discouraged in 6.7+. Many legacy plugins still write custom joins or raw queries, which break when schema changes occur or when multi-store/tenant mode is enabled. Shopware enforces repository-based access for a reason: context awareness, entitlement checking, and transaction safety.
// ✅ Correct approach in 6.7+
$productData = $this->entityRepository->search(
(new Criteria())->addFilter(new EqualsFilter('active', true)),
$context
)->getEntities();
// ❌ Avoid this pattern entirely
$connection = Connection::getConnectionFromContainer($container);
$results = $connection->fetchAll('SELECT * FROM product WHERE active = 1');
Even when writing custom data, use AbstractRepository or register a new repository service. If you must interact with non-Shopware tables, query them via a dedicated connection pool rather than the core database context. This keeps your plugin portable and future-proof.
4. Overlooking Manifest Structure & Plugin Registration Validation
The manifest.xml is no longer just metadata; it’s a contract. Shopware 6.7 validates schemas strictly, requiring exact namespace alignment, platform version constraints, and proper component registration. A frequent mistake is omitting required tags like <components> or misdeclaring bundle paths, causing silent installation failures or admin SDK mismatches.
<shopware-component xmlns="https://store.shopware.com/shopware-manifest-schema"
vendor="YourVendor">
<id>your-plugin-id</id>
<label>Your Plugin</label>
<version>1.0.0</version>
<license>MIT</license>
<description>A robust extension for Shopware 6.7+</description>
<components>
<component name="CustomAdminSdkExtension"/>
</components>
<copyright>© 2024 YourVendor</copyright>
</shopware-component>
Notice the strict xmlns, explicit <id> matching the plugin folder, and component registration. In 6.7+, missing or malformed components prevent admin SDK registration and break CLI updates. Always run bin/build-manifest.sh locally before deployment to catch schema violations early.
Conclusion
Building Shopware 6.7 plugins requires embracing modern Symfony conventions, repository-driven data access, and asynchronous messaging. The platform’s increased strictness protects developers from subtle bugs but demands discipline upfront. Prioritize constructor DI, route heavy work through Messenger, never bypass entity repositories, and validate your manifest against Shopware’s schema. By aligning with these patterns, your extensions will remain stable, performant, and upgrade-ready across Shopware’s evolving ecosystem. Test thoroughly with bin/phpunit, monitor deprecation logs, and let the framework guide your architecture rather than fighting it.