From sw6-skills
Shopware 6 development conventions and patterns for client projects. Trigger whenever the user works on Shopware 6 code, plugins, or configuration.
How this skill is triggered — by the user, by Claude, or both
Slash command
/sw6-skills:shopware6The summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill provides conventions, architectural decisions, and gotchas for
This skill provides conventions, architectural decisions, and gotchas for Shopware 6 client projects. For standard "how to" questions, refer to the official docs above — they are comprehensive. This file focuses on what the docs don't tell you.
Apply these to all plugin PHP code:
else / elseif — guard clauses and early returns onlyinstanceof checks over loose truthy checkspublic const string NAME = 'value'; (PHP 8.3+)private readonly FooService $foostyle= in Twig — use Bootstrap utility classes (d-none, d-flex, etc.) in storefront, Shopware grid system in admin!important in CSS/SCSS — use more specific selectors insteadAll plugins live in custom/static-plugins/ and must be Composer-installed.
"repositories": [
{ "type": "path", "url": "custom/static-plugins/*", "options": { "symlink": true } }
]
Then: composer require myvendor/my-plugin:*
Do not use custom/plugins/. Plugins there require a database to resolve,
which breaks shopware-cli project ci and any build pipeline without DB access.
Target the minor versions you support:
"require": {
"shopware/core": "~6.6.0 || ~6.7.0"
}
Implement ThemeInterface on the base class. Configuration goes in
Resources/theme.json, not PHP. Refer to:
https://developer.shopware.com/docs/guides/plugins/themes/
Reference: https://symfony.com/doc/7.4/service_container/autowiring.html
Use autowiring for everything. Symfony 7.4 eliminates the need for explicit service XML in virtually all cases.
<?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>
<defaults autowire="true" autoconfigure="true" public="false"/>
<prototype namespace="MyVendor\MyPlugin\" resource="../../*">
<exclude>../../Resources</exclude>
<exclude>../../MyPlugin.php</exclude>
</prototype>
</services>
</container>
Shopware autowires repositories by parameter naming convention —
$<camelCaseEntityName>Repository:
public function __construct(
private readonly EntityRepository $productRepository, // product.repository
private readonly EntityRepository $orderLineItemRepository, // order_line_item.repository
private readonly EntityRepository $myCustomEntityRepository, // my_custom_entity.repository
) {}
This works for all entity repositories including custom ones. No #[Autowire]
attribute or XML needed.
Use PHP attributes, not XML:
use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
#[AsDecorator(decorates: ProductDetailRoute::class)]
class MyProductDetailRoute extends AbstractProductDetailRoute
{
public function __construct(
#[AutowireDecorated]
private readonly AbstractProductDetailRoute $inner,
) {}
}
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
#[AsEventListener(event: ProductPageLoadedEvent::class)]
class MyListener
{
public function __invoke(ProductPageLoadedEvent $event): void { /* ... */ }
}
Or implement EventSubscriberInterface — autoconfigure handles the tag.
Prefer entity extensions. Custom fields are translatable by default, which
adds overhead (extra joins on _translation tables) and causes unwanted effects
for attributes that don't need translation (flags, external IDs, numeric values).
Use custom fields only when:
Use entity extensions for everything else:
| Situation | Use |
|---|---|
| Headless / PWA consumers | Store API route (_routeScope: store-api) |
| Server-rendered Twig pages | Storefront controller (_routeScope: storefront) |
| Admin panel integrations | Admin API route (_routeScope: api) |
| AJAX from storefront JS | Store API route (preferred) or storefront with XmlHttpRequest: true |
Dispatch to the message queue for anything that:
When overriding a Shopware Twig block, add a comment with the original block's content hash and the Shopware version. This makes it easy to detect when an upstream change breaks your override:
{# shopware-block: [email protected] #}
{% sw_extends '@Storefront/storefront/page/product-detail/index.html.twig' %}
{% block page_product_detail_buy %}
{{ parent() }}
<div class="my-addition">...</div>
{% endblock %}
Generate the hash: sha256sum of the original block content, first 12 chars.
Shopware 6.7+ removed CSRF tokens entirely. Do not add them to forms.
When working with product data via Store API includes or in Twig, always include
both name and translated fields. The translated object contains the
resolved translation; the root name may be the parent product's value
(variant inheritance).
Key conventions beyond the docs:
BINARY(16) — Shopware UUIDsDATETIME(3) — millisecond precisioncreated_at DATETIME(3) NOT NULL and updated_at DATETIME(3) NULLCREATE TABLE IF NOT EXISTS / ADD COLUMN IF NOT EXISTS for idempotent migrationsfk.<table>.<column>AB-123 → AB) requires decorating ProductSearchBuilderInterfaceproduct_search_keyword is NOT updated on incremental writes — a full reindex is neededes:index during business hours on catalogs > 50k productsthrow_exception: false in OpenSearch config is recommended for production — it falls back to DAL on ES failureCriteria::setLimit() — unbounded queries on large catalogs will time outRead in PHP:
// Key format: PluginTechnicalName.config.fieldName
$value = $this->systemConfigService->get('MyPlugin.config.apiKey');
$value = $this->systemConfigService->get('MyPlugin.config.apiKey', $salesChannelId);
shopware:
deployment:
runtime_extension_management: false # prevent plugin installs from admin
framework:
cache:
app: cache.adapter.redis
default_redis_provider: '%env(REDIS_URL)%'
session:
handler_id: '%env(REDIS_URL)%'
For S3/object storage configuration, refer to: https://developer.shopware.com/docs/guides/hosting/infrastructure/filesystem.html
| Trap | What happens | Fix |
|---|---|---|
Context::createDefaultContext() in storefront | Bypasses sales channel rules, prices, permissions | Use injected SalesChannelContext |
BLUE_GREEN_DEPLOYMENT=1 without two DB users | Silent failures on deploy | Set to 0 or configure two DB users |
| Editing compiled CSS directly | Overwritten on next theme:compile | Edit SCSS source files |
No --time-limit on messenger workers | Memory leaks, eventual OOM kill | Always set --time-limit=300 + supervisor restart |
Running es:index during peak traffic | Locks tables, degrades performance | Schedule during off-hours |
customFields for relational data | No joins, no Criteria filtering, poor performance | Use entity extension |
Assuming product.name is translated | Returns parent value on variants | Use product.translated.name |
# Build (CI/CD only — never run locally, it removes source files)
# shopware-cli project ci # use in Dockerfile / pipeline only
# Plugin lifecycle
bin/console plugin:refresh
bin/console plugin:install --activate MyPlugin
# Cache & assets
bin/console cache:clear
bin/console theme:compile
bin/console assets:install
# Database
bin/console database:migrate --all
bin/console database:migrate --all MyPlugin
# Queue
bin/console messenger:consume async low_priority --time-limit=300 --memory-limit=512M
bin/console scheduled-task:run
# Search
bin/console es:index
bin/console dal:refresh:index
# Debug
bin/console debug:container | grep product
bin/console debug:event-dispatcher
bin/console debug:router | grep my-plugin
npx claudepluginhub vanwittlaer/sw6-skills --plugin sw6-skillsProvides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.