Introduction

Shopware 6 has continuously evolved since its initial release, introducing architectural refinements, stricter security boundaries, and enhanced developer tooling with each major version. For merchants and agencies relying on custom plugins, upgrading beyond version 6.4 is rarely a simple drop-in replacement. It requires intentional refactoring, updated service definitions, and alignment with modern Symfony patterns. When targeting Shopware 6.7 and beyond, migration becomes both more complex and significantly more rewarding. This guide explores what changes between major versions, how to approach plugin migration systematically, and which patterns guarantee compatibility with Shopware 6.7+ standards.

Understanding Shopware 6’s Architectural Evolution

Major Shopware 6 upgrades do not merely patch functionality; they shift underlying contracts. Version 6.4 standardized the modern plugin directory structure and pushed dependency injection over legacy service overrides. Version 6.5 tightened event tagging, enforced stricter routing conventions, and deprecated numerous Twig filters. By 6.6 and 6.7, the platform solidified its reliance on PHP 8.2+, Symfony 6.4+, and strict type declarations. Service discovery moved fully to XML-based services.xml definitions with explicit tags. Administration extensions transitioned to Vue 3 with TypeScript typings, while storefront routing embraced PHP attributes over annotations. Custom plugins that still reference hardcoded container calls, legacy bundle namespaces, or annotation-based controllers will fail silently or throw fatal errors in 6.7 environments. Fortunately, Shopware’s deprecation logger and migration tooling provide a clear path forward.

Critical Areas Requiring Attention During Migration

When crossing major version boundaries, focus your audit on these core systems:

  • Plugin Structure & Autoloading: Modern plugins must follow src/ conventions with explicit namespace mapping. Legacy Custom/ paths are unsupported in 6.7+.
  • Service Registration & Injection: Direct $this->container->get() usage is deprecated. All services require constructor injection and explicit XML tagging.
  • Routing & Controllers: Annotations have been replaced by PHP 8 attributes (#[Route], #[RouteAttribute]). Controllers should extend AbstractSalesRoute or AbstractController.
  • Templating & Snippets: Twig inheritance uses absolute namespace paths (@Storefront/storefront/...). Snippet overrides follow strict src/Resources/snippets/ layering.
  • Events & CLI Commands: Listeners require precise tag attributes. Commands must declare their executable name via the console.command service tag.
  • Administration Extensions: UI modules now demand proper module registration, TypeScript interfaces, and alignment with Shopware’s official SDK versioning policy.

Step-by-Step Migration Workflow

Successful migration starts in staging, never production. Begin by enabling Shopware’s deprecation logger to surface legacy patterns. Run ./bin/deprecation:log during frontend requests, CLI commands, and admin interactions to catalog deprecated calls. Next, audit your plugin against the official migration guides for each version you’re skipping. Refactor services to use constructor injection, replace annotations with attributes, and update template inheritance syntax. Pin your composer.json minimum Shopware requirement to prevent accidental installations on incompatible storefronts. Finally, validate compatibility using Shopware’s upgrade wizard before scheduling deployment.

Shopware 6.7+ Code Examples Explained

Modern plugins must embrace explicit contracts. Below are examples aligned with Shopware 6.7 standards:

Service & Event Listener Registration (services.xml)

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

    <service id="MyPlugin\Handler\CustomCartTaxHandler">
        <argument type="service" id="Swag\Platform\Framework\Cart\CartCalculator"/>
        <tag name="kernel.event_listener" event="shopware.cart.after_calculate_taxes" method="onAfterCalculateTaxes"/>
    </service>

    <service id="MyPlugin\Command\CacheWarmupCommand">
        <tag name="console.command" command="myplugin:cache:warmup"/>
    </service>
</container>

This setup ensures proper service discovery, prevents circular dependencies, and aligns with Symfony 6.4+ expectations. The CLI command explicitly declares its executable signature, while the event listener ties directly to a verified Shopware 6.7 cart event.

Modern Controller with PHP Attributes (CustomApiController.php)

<?php declare(strict_types=1);

namespace MyPlugin\Controller;

use Shopware\Core\Framework\Routing\AbstractSalesRoute;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;

#[Route(defaults: ['_routeScope' => ['storefront']])]
class CustomApiController extends AbstractSalesRoute
{
    #[Route('/api/custom-endpoint', name: 'api.custom.endpoint', defaults: ['_acl' => ['frontend']], methods: ['GET'])]
    public function getCustomData(): JsonResponse
    {
        return $this->createJsonResponse(['status' => 'active']);
    }
}

Notice the complete removal of annotations, explicit route scopes, and strict typing. Shopware 6.7 enforces these patterns to improve routing performance and security validation. The controller also inherits from AbstractSalesRoute, granting access to framework utilities without direct service container manipulation.

Avoiding Common Migration Traps

Two pitfalls consistently derail plugin upgrades. First, ignoring deprecation logs until upgrade day forces rushed refactoring. Treat every deprecation warning as a breaking change in 6.7+. Second, assuming administration extensions work identically across versions. The 6.7 admin enforces strict module registration files and requires package.json alignment with Shopware’s core SDK. Always test admin UI changes in isolated environments using the official development scaffolding. Additionally, version your plugin’s dependencies carefully. Mixing incompatible Symfony packages or outdated Twig bridges will trigger fatal class resolution errors at runtime.

Testing, Deployment & Maintenance

Migration validity depends on rigorous validation. Run unit tests for service logic, integration tests for event listeners and CLI commands, and UI regression tests for administration modules. Use Shopware’s test framework with dedicated database fixtures to simulate real product and cart flows. When deploying, schedule upgrades during low-traffic windows, clear compiled caches (php bin/cache:clear), and rebuild administration assets (./bin/build-administration.sh). Monitor var/log/production.log immediately post-upgrade for missing services or routing mismatches. Finally, document your plugin’s minimum Shopware version requirement in metadata to prevent future compatibility confusion.

Conclusion

Migrating custom plugins across major Shopware 6 versions demands discipline, but it unlocks substantial performance and security gains. By embracing explicit dependency injection, modern PHP attributes, strict typing, and Shopware 6.7’s enhanced tooling, your extensions will remain resilient and maintainable. Treat migration as a refactoring opportunity rather than a compliance hurdle. The platform’s architecture is deliberately moving toward stricter contracts and developer-friendly standards, making this the ideal time to future-proof your custom plugins for Shopware 6.7 and beyond.