Shopware 6 Plugin Development Best Practices: DAL, Services, Events, Admin Modules
- Jay Patel
- Shopware 6
- Jan 22, 2026
- Reading time: 10 minutes
If your Shopware plugin works today but breaks on updates, slows down the admin, or turns into a “do not touch” codebase, it’s almost always the same root cause: business logic is mixed into Shopware glue.
This guide shares practical best practices for Shopware 6.7+ plugin development across the DAL, service layers, event subscribers, and administration modules. It’s written for teams building production plugins for EU and global stores.
Quick answer: The most upgrade-safe approach is thin Shopware glue + a clean service layer + disciplined DAL usage + small, predictable admin modules.
Want a battle-tested build or code review? Shopware development services or talk to MageSpark.
The core rule: keep Shopware glue thin
Plugins that age well separate Shopware-specific wiring from your business logic. Think in two layers:
- Shopware glue: event subscribers, controllers, DAL repository calls, admin module registration
- Business logic: services, validators, mappers, calculators, domain rules
Your glue will change when Shopware evolves. Your business logic should stay stable, testable, and easy to reason about.
DAL best practices that prevent performance pain
The DAL is powerful, but it’s also where plugins accidentally create slow admin grids, heavy queries, and painful imports. If you only read one DAL resource, start with the official DAL concept docs: Shopware Data Abstraction Layer (DAL) .
1) Model entities intentionally (avoid “dumping ground” entities)
Keep entities focused. A mega-entity with endless fields and associations becomes slow and hard to maintain. Add associations only when you have a real use case that needs them.
2) Use repositories and Criteria with intent
Most production DAL issues in plugins come from over-fetching and N+1 queries. Your goal is to load only what the use case needs, no more.
- Load only required associations (avoid pulling full graphs “just in case”).
- Batch writes where possible (don’t update 500 rows in a loop).
- Use limits/pagination for admin lists and exports.
<?php
$criteria = new Criteria([$id]);
$criteria->addAssociation('media');
$criteria->setLimit(1);
// Keep it tight. Add associations only when the use case truly needs them.
3) Migrations: make them deterministic and safe on big datasets
A migration that feels instant on a dev DB can lock tables in production. Keep migrations idempotent, explicit, and mindful of long-running operations.
- Safe to run again (idempotent)
- Avoid long locks and heavy backfills during install/update
- Separate schema changes from heavy data migrations when possible
Service layer best practices (this is where maintainability comes from)
Event subscribers and controllers should not contain business logic. They should validate context and call a service. Services should do the work.
- One public entrypoint per use case (example:
OrderSyncService::sync()) - No container injection (inject real dependencies)
- No I/O in constructors (no DB calls, no HTTP calls)
- Make side effects obvious (writes and external calls should be easy to spot)
<?php
final class OrderPlacedSubscriber implements EventSubscriberInterface
{
public function __construct(private readonly OrderSyncService $orderSyncService) {}
public static function getSubscribedEvents(): array
{
return [
// Replace with the exact event you need for your project
'checkout.order.placed' => 'onOrderPlaced',
];
}
public function onOrderPlaced($event): void
{
// Thin glue: validate + route to a service.
$this->orderSyncService->syncFromEvent($event);
}
}
Events best practices: extend without creating hidden behavior
Shopware uses Symfony’s event system. If you want a clean baseline, use the official guide: Listening to Events (Shopware docs) .
1) Exit early and keep subscribers cheap
- Exit early when the event isn’t relevant
- Avoid heavy work inside subscriber methods
- Move heavy tasks to a service (and async when appropriate)
2) Be careful with DAL events (they can fire a lot)
DAL events can trigger frequently during imports, admin edits, and integrations. Guard by context and changed fields. If you need deep processing, queue it.
Administration modules: build it like native Shopware
A good admin module feels native: fast, permission-aware, translated, and stable across updates. For the clean “Shopware way” to add a module, follow the official guide: Add custom module (Shopware Administration) .
1) Use a predictable module structure
Resources/app/administration/src/main.js
Resources/app/administration/src/module/your-module/index.js
Resources/app/administration/src/module/your-module/page/...
Resources/app/administration/src/module/your-module/component/...
Resources/app/administration/src/module/your-module/snippet/en-GB.json
Resources/app/administration/src/module/your-module/snippet/de-DE.json
For DACH shops, bilingual admin labels (EN + DE) reduce friction for mixed teams and client stakeholders.
2) Keep business logic out of Vue components
Vue components should handle UI state and user interaction. Real business rules belong in backend services. That keeps behavior consistent across storefront, admin, and API calls.
3) Make permissions and error states obvious
Hide actions when the user lacks rights. Return clear API errors that map to helpful UI messages. This alone can cut support tickets significantly.
Configuration: use plugin config pages when you can
If you only need settings, don’t build a full admin module. Use the built-in plugin configuration first: Add plugin configuration (config.xml) .
- Use plugin config for: toggles, keys, small option lists
- Use admin modules for: CRUD screens, workflows, dashboards, complex forms
Store-ready quality checks (what clients and reviewers actually care about)
- No hard-coded credentials, debug flags, or dev-only behavior
- Graceful failures (clear messages, no silent breaks)
- Clean uninstall behavior (document what gets removed vs retained)
- Translations for admin labels (at least EN + DE for DACH teams)
- Safe migrations and predictable update paths
- Permissions respected (ACL) and UX is clear
- Documentation: configuration, limitations, and support boundaries
FAQs
What is the best architecture for a Shopware 6 plugin?
Thin Shopware glue (subscribers, controllers, DAL calls) plus a service layer that owns your business logic. This keeps upgrades easier and makes your logic testable.
Should I put logic in event subscribers?
Keep subscribers thin. Use them to validate context and call a service. Put real logic in a service so it can be tested and reused.
How do I avoid DAL performance issues?
Don’t over-fetch. Avoid large association graphs, prevent N+1 queries, batch writes, and keep heavy work out of high-frequency DAL events.
Need a plugin built cleanly for production?
If you want an architecture review, performance check, or a scoped build estimate, explore Shopware plugin development or contact MageSpark.
For the fastest estimate, share your Shopware version, the feature list, and whether you need a custom admin module or only config.
