Download Latest Version Release v6.6.10.31 source code.zip (21.9 MB)
Home / v6.7.16.0
Name Modified Size InfoDownloads / Week
Parent folder
README.md 2026-10-22 124.6 kB
Release v6.7.16.0 source code.tar.gz 2026-10-22 14.5 MB
Release v6.7.16.0 source code.zip 2026-10-22 23.5 MB
Totals: 3 Items   38.1 MB 0

System requirements

  • tested on PHP 8.2, 8.4 and 8.5
  • tested on MySQL 8, MySQL 9.7, MariaDB 11 and MariaDB 12

Critical Fixes

Theme configuration updates reach all descendants

Saving a base theme now updates inherited runtime configuration and recompiles assigned descendant themes at every inheritance level. Child and grandchild themes no longer require a separate save after changing their base theme, and descendant-specific overrides are preserved. Deactivating or uninstalling a base theme is also blocked while any descendant theme or theme copy is assigned to a sales channel. The error identifies the affected themes and sales channels; remove or replace those assignments before removing the base theme.

Content Security Policy for static HTML and SVG assets on Apache

The Apache .htaccess.dist template now sends script-src 'none'; object-src 'none'; frame-src 'none'; base-uri 'none'; form-action 'none' for existing .html files under public/media and public/thumbnail, and for SVG files. Application-rendered HTML and bundled HTML applications retain their existing behavior. Ensure your deployed .htaccess contains the updated Shopware section; installations without the Shopware update markers must merge it manually. Custom static documents must work without scripts, embedded documents, base URL overrides or form submissions; images and styles remain permitted.

Sales channel contexts expose the default currency again

SalesChannelContext::getSalesChannel()->getCurrency() returns the sales channel's default currency again, including when another currency is selected for the context. This restores the behavior before 6.7.15.0 for extensions that read the default currency. Use SalesChannelContext::getCurrency() for the currently selected currency.

Line item conditions evaluate line items by the data they carry

Since 6.7.14.0, most line item conditions of the Rule Builder evaluated only line items of the type product. Custom and credit line items and line items that extensions add to the cart no longer matched them, and a single custom line item could hide shipping methods or block promotions under a negated condition such as "Item with tag / All / Are none of".

The conditions now evaluate a line item by the data it carries instead of by its type, so line items of any type work with the built-in conditions again, with no change needed in extensions.

"Item with price/list price ratio" no longer matches line items without a list price

Since 6.7.9.0, the Rule Builder condition "Item with price/list price ratio" treated line items without a list price as having a ratio of 0. Conditions such as "Is less than 1" therefore also matched every line item without a list price, so promotions meant for reduced products applied to all products.

Line items without a list price no longer have a ratio, so only the operators "Is not equal to" and "Is empty" match them. If you used "Is equal to 0" to target line items without a list price, use "Is empty" instead.

Features

Digital products can use external file assignment

In the Administration, digital products can be saved without uploaded files when file assignment is handled externally. When editing a product, the option is enabled by default if it has no downloads and disabled if it has downloads.

System configuration tabs

With the newly added tabs feature, plugin developers can now add another layer of organization to the already existing cards in the system configuration. This allows to group related cards into individual tabs and provide a better overview for merchants when configuring a plugin. The feature is fully optional to use and works with partial usage as well - any cards not added to a tab are automatically gathered in a "General" tab.

Example can be found here

Core

Document-type deprecations

Constructing the deprecated document-type definitions, entities and collections, including empty collections, throws when v6.9.0.0 is active. Their getters and setters emit no deprecation notices. Use document.typeName and the document v2 type registry instead of the legacy DAL entities.

Import/export profile translation construction in major mode

Constructing the deprecated ImportExportProfileTranslationDefinition, ImportExportProfileTranslationEntity or ImportExportProfileTranslationCollection, including an empty collection, now throws when v6.8.0.0 is active. All remain usable before removal, without per-accessor deprecation notices. Profile label translations are being removed in 6.8; use the profile's technical name instead.

Invalid price definition exception factory

Use DataAbstractionLayerException::invalidPriceFieldType() instead of constructing the deprecated InvalidPriceFieldTypeException. The factory preserves the legacy exception in 6.7 and returns DataAbstractionLayerException with the same error code and message when v6.8.0.0 is active.

Preview three 6.8 behavior changes independently

Set MEDIA_URL_PATH_ENCODING=1 to test encoded media URL paths, PROPORTIONAL_CART_TAXES=1 to test proportional tax calculation for percentage prices and split line items, or DELETE_CART_AFTER_ORDER_CREATION=1 to test the earlier persisted-cart deletion during checkout. Each flag can be enabled without the other 6.8 changes. When V6_8_0_0=1, all three activate unless explicitly disabled. The corresponding migration guidance is in UPGRADE-6.8.md.

New ProductBackInStockEvent replaces ProductNoLongerAvailableEvent for products that become available

The new Shopware\Core\Content\Product\Events\ProductBackInStockEvent is dispatched with the ids of products whose available flag changed from false to true, for example after a restock of a closeout product.

Shopware\Core\Content\Product\Events\ProductNoLongerAvailableEvent now only contains products whose available flag changed from true to false. If your subscriber reacts to both directions, for example to invalidate caches, subscribe to both events.

When HreflangLoaderParameter is constructed without the $homepage argument, isHomepage() again detects the homepage from the frontend.home.page route name, so homepage hreflang links are generated for these callers. This fallback is deprecated and will be removed in 6.8. If you construct HreflangLoaderParameter yourself, pass $homepage explicitly.

Snippets can be provided through the private filesystem

Administration and storefront snippets are now also loaded from the private filesystem (shopware.filesystem.private, by default files/):

  • Administration: snippets/administration/<source>/<language>.json (or <locale>.json), for example files/snippets/administration/MyIntegration/de.json
  • Storefront: snippets/storefront/<source>/<name>.<language>.json (or .<locale>.json, optionally .base.json), for example files/snippets/storefront/MyIntegration/storefront.de.json

They form the lowest-priority layer, so snippet files shipped by the core, plugins or apps always win. Use them for keys nobody else provides. The source directory becomes the author and technical name of storefront snippets. Files survive updates and deployments and are never cleaned up by Shopware, except for the administration subdirectories it writes itself for themes, which are named after the theme's technical name, so pick a different source name. Run cache:clear after adding or changing a file (for administration snippets, invalidating the admin-snippet cache tag is enough). The constants live in Shopware\Core\System\Snippet\Files\FilesystemAdministrationSnippets and FilesystemStorefrontSnippets. snippet:validate ignores these files.

Asset installation on S3-compatible storage

Asset installation now overwrites existing files without deleting their directory first when using --force or rebuilding a missing asset manifest. This prevents delayed storage deletions from removing freshly uploaded files. Obsolete files are still removed, and no configuration changes are required.

Deactivating a plugin now removes its asset manifest entry but retains its public files until uninstall. This avoids a pending directory deletion removing files uploaded after reactivation. Uninstall still removes the plugin's public files.

Product stream builders can migrate without dropping the legacy contract

AbstractProductStreamBuilder now implements the deprecated ProductStreamBuilderInterface and forwards buildFilters() to enrichCriteria(). Extensions can therefore migrate their implementations to the abstract class while remaining compatible with code that still consumes the legacy interface.

Dynamic product groups without usable filters

Cross-selling, category listings and CMS product sliders no longer fail on such a group. An empty group selects all products, like the product export, and a broken one selects none and logs a warning.

Feature flags can belong to a major version

Feature flags such as JSON_LD_DATA and CACHE_REWORK now activate automatically when V6_8_0_0=1 is set. An explicit setting for the individual flag still takes precedence, so JSON_LD_DATA=0 keeps that feature off. Standalone major flags are recognized by their version-shaped names; the major field is only for sub-features and must name a parent version flag. Flags without a parent omit major from their metadata and the feature-flag API response. FEATURE_ALL now activates every registered feature for any truthy value; use a version flag to test only that major's changes.

Order age rule condition

The Rule Builder condition orderDaysSincePlaced compares the order's orderDate with the current date in calendar days. It supports numeric comparison operators and requires a numeric daysPassed value, for example operator: <= and daysPassed: 30 to match orders placed within the last 30 days.

Extensions can evaluate Shopware\Core\Content\Flow\Rule\OrderDaysSincePlacedRule in a FlowRuleScope or a derived scope to apply date limits to existing orders.

Unlimited DAL searches with next-pages totals

Database-backed DAL searches using Criteria::TOTAL_COUNT_MODE_NEXT_PAGES without a limit now return all matching entities after the requested offset and report the exact total, as TOTAL_COUNT_MODE_EXACT does. Previously, these searches returned at most one entity. Searches with a limit continue to use bounded lookahead totals.

System activity logging

System activities now produce Monolog records at the info level on the system_activity channel. By default, records are stored in the log_entry database table through the buffered business-event handler. You can also route the channel to a file or another logging destination.

More details can be found here

Feed sales channels are saved without a currency list again

Sales channels of types other than storefront and headless, such as product comparison, Agentic Commerce and types added by extensions, are no longer rejected with SYSTEM__NO_GIVEN_DEFAULT_CURRENCY_ID when their default currency is missing from their currency list, as they were since 6.7.15.0. Storefront and headless sales channels still need their default currency in their currency list.

Filtered listings show the main variant only if it matches the active filters

Filtered product listings show a variant product's main variant only if it matches all active filters, such as property, price or manufacturer filters. Otherwise, a matching variant is shown. Products configured to display their parent always show the parent.

core.listing.findBestVariant now only affects search results. With it enabled, filtered listings show a matching main variant or the parent instead of another matching variant.

Extensions that replace the preview resolution via LoadPreviewExtension can read the active post filters from the new postFilters property to apply the same rule.

Feature flags can remove legacy service definitions

Extensions can tag a PHP service definition with shopware.inactiveFeature and a flag attribute, for example v6.8.0.0. The service remains registered while the flag is inactive and is absent from the container once the flag is active. Use this for services that are removed with a major version; shopware.feature continues to register services only while their flag is active. Changing FEATURE_ALL or a version-shaped major flag in the environment selects a separate container on a fresh kernel boot or explicit reboot when the default build directory is used. If APP_BUILD_DIR is configured, provide a different directory for each major mode. Reboot the kernel or restart long-running processes to apply the new mode.

Deprecated service aliases with an announced removal version are removed when that major flag becomes active. Their target services remain available.

System config schema endpoints now require system config read access

The deprecated endpoint GET /api/_action/system-config/schema and its successor GET /api/_action/system-config/get-schema now require the existing system_config:read privilege. Integrations and API clients that call these endpoints must add this privilege to their ACL role.

Deprecation of legacy ConfigurationService getters

The getConfiguration() and getResolvedConfiguration() methods of Shopware\Core\System\SystemConfig\Service\ConfigurationService are deprecated and will be removed in Shopware 6.8. Use getSystemConfigDefinition() and getResolvedSystemConfigDefinition(), respectively.

Property sorting keeps its default locale

The BC attribute for PropertyGroupCollection::sortByConfig() now correctly marks the upcoming localeCode parameter as optional, with the default 'en_GB'. Calls without arguments remain compatible with 6.8. Overrides need to declare the optional parameter when the parent signature changes.

Array values in static system configuration

shopware.system_config entries in config/packages now accept arrays, including []. An empty sales-channel value clears an array from the default scope; a more specific sales-channel key still overrides an empty default parent.

VAT ID constraints of the registration can be modified via BuildValidationEvent

RegisterRoute now adds the vatIds constraints (NotBlank for countries with a required VAT ID, Type and CustomerVatIdentification) before dispatching framework.validation.customer.create. Subscribers of this event can now adjust or remove them, for example to make the VAT ID optional for certain business customers. Previously the constraints were added after the event and could not be changed. This matches the existing behaviour of ChangeCustomerProfileRoute.

JsonField supports typed properties with additional extension data

JsonField accepts the new allowAdditionalProperties: true constructor argument. Use it for a JSON field with stable, mapped properties whose types should be validated while extension-owned keys must remain writable:

:::php
new JsonField('config', 'config', [new IntField('position', 'position')], allowAdditionalProperties: true);

Mapped properties continue through their field serializers; additional properties are retained unchanged.

Plain text fields are sanitized with HTMLPurifier

StringField and LongTextField values without the AllowHtml flag are now sanitized with HTMLPurifier instead of PHP's strip_tags(). A < that does not start a tag is kept, so I <3 Kisses or 5 < 10 are stored as typed. The text inside removed <script> and <style> elements is dropped instead of being stored, and HTML entities such as &lt; stay verbatim. A < directly followed by a letter still starts a tag and is removed.

A value consisting only of markup now sanitizes to an empty string; on a required field the write is rejected with a constraint violation.

Extensions can apply the same behaviour with the new Shopware\Core\Framework\Util\HtmlSanitizer::stripTags().

Search keeps the text behind a stray <

The search tokenizer used strip_tags() as well, so a product named I <3 Kisses was indexed as i and was not found by searching for "kisses". Product names, search terms and every other tokenized field now keep everything a < cannot turn into a tag. Run bin/console dal:refresh:index to rebuild the search keywords of existing products.

Dompdf page count placeholder replaced for core and fallback fonts

In PDF document generation, Dompdf falls back to standard 14 built-in AFM fonts (such as Helvetica) when external web fonts are unavailable behind a firewall, or when documents are styled with core PDF fonts. Dompdf encodes those fonts using single-byte strings instead of UTF-16BE. PdfRenderer now replaces both encodings in the CPDF stream, ensuring DOMPDF_PAGE_COUNT_PLACEHOLDER is reliably replaced with the actual total page count regardless of active font encoding or network availability.

Moved PHP classes retain backwards-compatible aliases

The following classes moved to their canonical Core namespaces. Their previous names remain available as runtime class aliases throughout 6.7 and are removed with 6.8:

Previous name Canonical name
Shopware\Administration\Controller\NotificationController Shopware\Core\Framework\Notification\Api\NotificationController
Shopware\Administration\Notification\NotificationCollection Shopware\Core\Framework\Notification\NotificationCollection
Shopware\Administration\Notification\NotificationDefinition Shopware\Core\Framework\Notification\NotificationDefinition
Shopware\Administration\Notification\NotificationEntity Shopware\Core\Framework\Notification\NotificationEntity
Shopware\Core\Framework\Plugin\Util\AssetService Shopware\Core\Framework\Adapter\Asset\AssetService
Shopware\Elasticsearch\Product\SearchConfigLoader Shopware\Core\Framework\DataAbstractionLayer\Search\SearchConfigLoader

Update imports, type declarations, static references, and service IDs to the canonical names. The aliases preserve runtime class identity during the transition; they do not create compatibility subclasses. NotificationController remains internal, and AssetService becomes internal with 6.8. Neither should be introduced as a new extension dependency.

Shared document classes moved to DocumentV2

The legacy document classes that document generation v2 keeps moved into Shopware\Core\Checkout\DocumentV2. Their previous names remain available as runtime class aliases throughout 6.7 and 6.8 and are removed with 6.9. The previous service IDs remain as deprecated service aliases for the same period.

Previous name Canonical name
Shopware\Core\Checkout\Document\DocumentEntity Shopware\Core\Checkout\DocumentV2\DocumentEntity
Shopware\Core\Checkout\Document\DocumentDefinition Shopware\Core\Checkout\DocumentV2\DocumentDefinition
Shopware\Core\Checkout\Document\DocumentCollection Shopware\Core\Checkout\DocumentV2\DocumentCollection
Shopware\Core\Checkout\Document\Aggregate\DocumentBaseConfig\DocumentBaseConfigEntity Shopware\Core\Checkout\DocumentV2\Aggregate\DocumentBaseConfig\DocumentBaseConfigEntity
Shopware\Core\Checkout\Document\Aggregate\DocumentBaseConfig\DocumentBaseConfigDefinition Shopware\Core\Checkout\DocumentV2\Aggregate\DocumentBaseConfig\DocumentBaseConfigDefinition
Shopware\Core\Checkout\Document\Aggregate\DocumentBaseConfig\DocumentBaseConfigCollection Shopware\Core\Checkout\DocumentV2\Aggregate\DocumentBaseConfig\DocumentBaseConfigCollection
Shopware\Core\Checkout\Document\Aggregate\DocumentBaseConfigSalesChannel\DocumentBaseConfigSalesChannelEntity Shopware\Core\Checkout\DocumentV2\Aggregate\DocumentBaseConfigSalesChannel\DocumentBaseConfigSalesChannelEntity
Shopware\Core\Checkout\Document\Aggregate\DocumentBaseConfigSalesChannel\DocumentBaseConfigSalesChannelDefinition Shopware\Core\Checkout\DocumentV2\Aggregate\DocumentBaseConfigSalesChannel\DocumentBaseConfigSalesChannelDefinition
Shopware\Core\Checkout\Document\Aggregate\DocumentBaseConfigSalesChannel\DocumentBaseConfigSalesChannelCollection Shopware\Core\Checkout\DocumentV2\Aggregate\DocumentBaseConfigSalesChannel\DocumentBaseConfigSalesChannelCollection
Shopware\Core\Checkout\Document\Renderer\RenderedDocument Shopware\Core\Checkout\DocumentV2\Struct\RenderedDocument
Shopware\Core\Checkout\Document\SalesChannel\AbstractDocumentRoute Shopware\Core\Checkout\DocumentV2\SalesChannel\AbstractDocumentRoute
Shopware\Core\Checkout\Document\SalesChannel\DocumentRoute Shopware\Core\Checkout\DocumentV2\SalesChannel\DocumentRoute
Shopware\Core\Checkout\Document\Service\ReferenceInvoiceLoader Shopware\Core\Checkout\DocumentV2\Service\ReferenceInvoiceLoader

Update imports, type declarations, static references, and service IDs to the canonical names. Entity names, repositories, and the /store-api/document/download route are unchanged.

A decorator of the route takes effect only when it decorates the canonical service ID. RenderedDocument::getApiAlias() keeps returning shopware_core_checkout_document_renderer_rendered_document until 6.9.

Merged document downloads have a speaking file name

Downloading several order documents at once from the order bulk edit delivered one merged PDF named with a 32 character random string, so merchants could not tell their downloads apart in the download folder.

The merged file is now named after its document type and the date of the download, for example delivery_note_2026-09-10.pdf. A download that mixes document types is called documents_<date>.pdf, and a single document keeps the name it was rendered with. Unlike the random string, that name is no longer unique per download.

An unset MCP allowlist no longer grants unrestricted MCP access

user.mcp_allowlist and integration.mcp_allowlist used to mean "everything is allowed" when they were unset, so every existing integration and non-admin user could reach the full MCP capability surface without anyone selecting it. They now mean the opposite: nothing is allowed until capabilities are selected explicitly. Only administrator users still bypass the allowlist; integrations never do.

Existing integrations and non-admin users therefore lose MCP access until an allowlist is granted, in the Administration under Settings > System > Integrations or on the user detail page.

Order contexts keep the order's own addresses

When an address of an order no longer matches an address of its customer, for example because the customer edited it, OrderConverter::assembleSalesChannelContext() now uses the order's own address instead of the customer's default address. This applies to the billing address and to the shipping address of the order's delivery. Orders without a delivery still fall back to the customer's default shipping address. Recalculations, flows and payment handling of that order therefore use the tax state, tax rules, cash rounding and address rules of the address the order was placed with.

In that case, getActiveBillingAddress() and getActiveShippingAddress() of the order context's customer return the order's address as a CustomerAddressEntity. Its ID is the ID of the order_address, not of a customer_address. Payment handlers and checkout gateway apps that load or update a customer address by this ID have to handle IDs that are not customer address IDs.

The options passed to AbstractSalesChannelContextFactory::create() and returned by BeforeSalesChannelContextAssembledEvent::getOptions() can now contain CustomerAddressEntity objects under the internal keys SalesChannelContextService::BILLING_ADDRESS and SalesChannelContextService::SHIPPING_ADDRESS. Decorators and listeners that read these options should not assume that every value is a string or an array.

Sales-channel scoped limits for system_config rate limiters

The cart setting "Maximum addable products to cart per minute through API" can be set per sales channel, but only the global value took effect. Per-sales-channel values now apply, a global value counts per sales channel instead of shop-wide, and cart-add counters restart once on update. Rate limiters with the system_config policy can resolve limits per sales channel when the caller passes the sales channel ID.

To make use of it, you can already pass the sales channel ID to the following class methods:

  • Shopware\Core\Framework\RateLimiter\RateLimiter::ensureAccepted()
  • Shopware\Core\Framework\RateLimiter\RateLimiterFactory::create()

The optional parameter will be part of the method signatures with 6.8.

Order transaction state machine gained a transition

The order transaction state machine now allows transitions from the state "unconfirmed" to "in_progress". This will allow async payment methods to leave the order transaction in "unconfirmed" after the pay step and transition to "in_progress" in the finalize step.

Promotion redemptions are recounted faster

Recounting a promotion's redemptions on order placement is faster, through a new index on order_line_item and a query that matches promotion line items by promotion_id alone.

Large MCP tool results from extensions are offloaded like core results

MCP tools from plugins and bundles that extend McpToolResponse now store results larger than 100 KB in the tool-result cache on the Admin API endpoint, like core tools already did. The response then contains _meta.resourceUri instead of the full data, and the model reads the data with resources/read. Before, these results were always returned inline. On the Store API endpoint, results stay inline until it has a tool-result resource to read them back.

The MCP discovery tool group is reserved for the core discovery tools

A fresh MCP session advertises the tools in the discovery group on every connection. That group is now limited to shopware-tool-search, shopware-toolsets-list and shopware-toolset-enable. A plugin or bundle tool that declares #[McpToolGroup('discovery')] is moved to the other toolset: it stays callable and can be enabled, but is no longer on the default surface. bin/console debug:mcp lists such tools, and the new PHPStan rule shopware.mcpReservedToolGroup reports them. To show your tools on the first tools/list, give them a group of their own and select it at connect time with ?toolsets=<group>.

agentic-commerce 1.3.0 and earlier put their UCP tools in discovery. Update to agentic-commerce 1.4.0 or later, which pins its own ucp toolset on /ucp/mcp; with an older version, UCP agents only see the discovery tools on the first tools/list.

dal:validate checks attribute entities

bin/console dal:validate no longer skips attribute entities. They are held to the same rules as EntityDefinition classes, for example that a many-to-one must not cascade deletes, and violations name them by their entity class instead of AttributeEntityDefinition, also when another definition's check mentions them. If your CI fails on dal:validate, or ignores messages that contain AttributeEntityDefinition, run it against your extension before updating.

SalesChannelContextRestorer::restoreByOrder() is deprecated

SalesChannelContextRestorer::restoreByOrder() now builds the context with OrderConverter::assembleSalesChannelContext() and re-evaluates the rules afterwards, as before. Its contexts therefore match every other order context: they use the customer addresses that match the order's addresses instead of the customer's default addresses, and dispatch BeforeSalesChannelContextAssembledEvent and SalesChannelContextAssembledEvent. They also keep the order's tax status, except that the re-evaluation still drops tax-free when the order no longer qualifies for it. Like the converter, it now fails with CHECKOUT__CUSTOMER_ADDRESS_NOT_FOUND when the order's billing address does not exist.

restoreByOrder() and SalesChannelContextRestorerOrderCriteriaEvent are deprecated and will be removed in 6.8.0.0. Load the order yourself and pass it to OrderConverter::assembleSalesChannelContext() instead.

Creating a language no longer fails on a drifted Elasticsearch/OpenSearch mapping

Creating a language could return an uncaught 500 when an Elasticsearch/OpenSearch-indexed entity's live index mapping had drifted from its current definition, for example a sales channel created after the last full reindex. LanguageSubscriber now catches the same known-unresolvable mapping conflicts IndexMappingUpdater already handles elsewhere, schedules the affected entity for a reindex instead of throwing, and only logs unexpected errors. The language is created successfully; the delayed reindex is picked up by the next indexing run or a manual es:index.

MCP servers run on symfony/mcp-bundle 0.14

Shopware now requires symfony/mcp-bundle 0.14.1, still on mcp/sdk 0.8. The extension tags and the registration described for 6.7.15.0 are unchanged.

One behaviour changes for plugins and bundles that register MCP tools, prompts or resources through the shopware.mcp.* or shopware.store_api_mcp.* tags: a % in their metadata, for example in a tool description or schema such as "5%-10%", is now kept as written. Before, the bundle read text between two % without whitespace in between as a container parameter placeholder, so the MCP endpoint failed with a "non-existent parameter" error, and a %% was shown as a single %. Text with a single %, or with whitespace between two % such as "between 5% and 10%", was not affected. If your plugin wrote %% to work around that, remove the workaround, otherwise the description now shows %%. App MCP capabilities are read from the app at runtime and are not affected.

MCP endpoints are rate-limited with a sliding window

mcp_admin_api and mcp_store_api in shopware.api.rate_limiter now use the sliding_window policy: 300 requests per minute per OAuth token on /api/_mcp, and 120 per minute per sales-channel context and per IP on /store-api/_mcp. Before, they used time_backoff, which accepts only one request per interval once the first limit is reached and keeps that state for an hour. A busy MCP client therefore got HTTP 429 on almost every call, including the handshake of a new session.

If you override these limits in your configuration, set policy, limit and interval for the new policy. The reset and limits keys only apply to time_backoff.

The additional 10-minute limits of the old configuration (1000 and 600 requests) are gone, so the sustained maximum is now 300 and 120 requests per minute.

Every Store API route publishes an extension event

All Store API routes in core now publish an extension event, so you can extend a route with a subscriber instead of decorating its abstract route class. Each route has a <Route>Extension in the Extension namespace of its domain that carries the route's input parameters, for example Shopware\Core\Content\Product\Extension\ProductListingRouteExtension:

:::php
public static function getSubscribedEvents(): array
{
    return [ProductListingRouteExtension::onPre() => 'addFilter'];
}

Use onPre() to change input objects such as the Criteria in place or to replace the result, onPost() to change the result, and onError() to provide a fallback. Decorating the abstract route classes keeps working.

Digital products follow their max. order quantity again

Digital products are no longer limited to one unit per order regardless of maxPurchase, as they were since 6.7.14.0. Digital products without a maxPurchase, for example created through the API, now fall back to core.cart.maxQuantity. Set maxPurchase to 1 to keep one unit per order.

GARAN labels in mails come from the garanLabels template variable

The order confirmation mail reads the GARAN label from the new garanLabels template variable. The sw_garan_label_mail Twig filter is deprecated. A migration updates the template for shops that never edited it.

If you customized the order confirmation mail, replace nestedItem.productId|sw_garan_label_mail(context) with garanLabels[nestedItem.productId] ?? null, and lineItem.productId|sw_garan_label_mail(context) with garanLabels[lineItem.productId] ?? null in the plain text version. garanLabels is passed to every mail template that references it and has an order in its data.

Order line items keep the GARAN label they were ordered with

When an order is placed, its product line items now store the GARAN label values in payload.garanLabel. The storefront shows the label the order was placed with. Editing an order keeps the stored values; products added while editing get their current label. Cart line items and order line items from before this change have no garanLabel key and show the product's current label.

Headless frontends can read the stored values from the order line item's payload instead of /store-api/product/{productId}/garan-label.

GARAN guarantee terms per product

Product's new fields for the guarantee terms, inherited by variants: guaranteeTermsMediaId for a PDF and guaranteeTermsUrl for a web page. guaranteeTermsUrl only accepts http:// and https:// URLs; other values are rejected with the INVALID_GARAN_GUARANTEE_TERMS_URL violation.

Each garanLabels entry has a new termsUrl key: the URL, or the PDF's URL if no URL is set. Mails that reference garanLabels attach the PDFs of the products. A migration adds the terms link to the order confirmation mail for shops that never edited it. If you customized the template, add the link below the GARAN label:

:::twig
{% if garanLabel.termsUrl %}<a href="{{ garanLabel.termsUrl }}">Guarantee terms</a>{% endif %}

The product detail page links the terms below the GARAN label, in the new block buy_widget_garan_label_terms_link.

Customer login publishes an extension event

AccountService::loginByCredentials(), which the login route uses, now publishes LoginByCredentialsExtension. Subscribe to LoginByCredentialsExtension::onPre() to check the credentials yourself, for example against an external identity provider: assign the context token to $extension->result and call stopPropagation().

Telemetry updates

  • Default metrics prefix was changed from io.opentelemetry.contrib.php.shopware to shopware.platform.
  • http.server.request.duration now shows client-facing latency without post-response processing.
  • cart.calculation.duration records failed calculations (added result=success|failed label).
  • Telemetry::instrument() now collects duration metrics also for failed callbacks.
  • Added support for lazy labels calculation in DurationMetric and ConfiguredMetric (see instrumentors for usage examples).
  • New methods Telemetry::isMetricEnabled() / Meter::isEnabled() saying if a metric would be emitted, so expensive calculations may be skipped for disabled metrics.
  • Metrics are flushed after all other terminate tasks.

Metrics are still behind the feature flag TELEMETRY_METRICS.

Order recalculation keeps an open payment transaction in sync with the order total

Recalculating an order, for example after editing it in the Administration, now moves the order's primary transaction to the new order total while the customer could still change the order's payment, so in the states of OrderService::ALLOWED_TRANSACTION_STATES: open, cancelled, reminded, failed, chargeback and unconfirmed. The transaction keeps its ID and state, and payment extensions reading order_transaction.amount of an order that isn't paid yet get the edited total instead of the amount the transaction was created with. Transactions in any other state keep their amount, also when the payment completes while the order is being edited.

OrderConverter::convertToCart() now converts the order's transactions too, with the primary transaction first. With the new cart permission CheckoutPermissions::KEEP_ORDER_TRANSACTION, the cart processor keeps that transaction and moves it to the cart total instead of creating a new one, and OrderConverter::convertToOrder() writes it back as an update of its amount and payment method.

Product reviews will require an author and a rating

With v6.8.0.0, POST /store-api/product/{productId}/review rejects a review without points, and the product_review entity marks externalUser and points as required and only accepts points from 1 to 5, so creating a review through the Admin API, the Sync API or a repository without them fails. The destructive migration step of the 6.8 update fills product_review.external_user for existing reviews without an author, with the first name of the review's customer or an empty string, sets points to 0 for existing reviews without a rating, and makes both columns NOT NULL. 0 points stands for a review without a rating: product.ratingAverage already ignores such reviews, like it ignores reviews without points today. Integrations, imports and headless frontends that create reviews should send both fields now, and those that read points should treat 0 like a missing rating; enable the v6.8.0.0 feature flag to test it.

FloatField accepts a value range

FloatField takes an optional minValue and maxValue, like IntField. A write with a value outside the range fails with a WriteConstraintViolationException.

Shared order restoration for Store API routes

Shopware\Core\Checkout\Cart\Order\OrderRestorer::restore() builds the sales channel context and cart of an existing order through OrderConverter, so its decorators and the context assembled events keep being invoked; addRequiredAssociations() adds the associations the order has to be loaded with. Read-only Store API routes opt in with the route default _allowOrderRestoration. For a request with an orderId, the restored objects are stored as sw-effective-sales-channel-context, sw-effective-context and sw-effective-cart and injected by the SalesChannelContext, Context, Cart and Criteria argument resolvers; the session attributes and sw-context-token stay untouched. A failing restoration answers CHECKOUT__ORDER_RESTORATION_FAILED.

AccountEditOrderPageLoader and SetPaymentOrderRoute take OrderRestorer instead of OrderConverter and CartService in their constructors; the restored cart they pass to the checkout gateway now carries the order's deliveries.

Sorting by product.price works with Elasticsearch

The product index now contains product.price, so sorting, filtering and aggregating on it work with Elasticsearch. Before, a sorting on product.price (e.g. the "Default price" listing sorting) failed with No mapping found for [price.c_....gross].

Run bin/console es:index after deploying. Existing documents have no price until they are reindexed.

Reduced remote thumbnail URL generation overhead

Remote thumbnail URL generation now avoids unnecessary extension dispatching when no listeners are registered. Existing extensions that listen to remote thumbnail URL events continue to work unchanged.

MCP list change notifications reach sessions on every server

When an app adds, changes or removes MCP tools, prompts or resources, every open MCP session on the Admin API endpoint now receives notifications/tools/list_changed (or the prompt or resource variant) after its next request, whichever server it is connected to. Before, only sessions known to the server that processed the app change were notified, because each server kept its own list of sessions.

The list of sessions is gone, together with the Shopware\Core\Framework\Mcp\Notification\McpSessionRegistry and mcp.store_api.session_registry services and their caches shopware.mcp.session_registry_cache and mcp.store_api.session_registry_cache. If you overrode them to share the list between servers, remove the override. A shared framework.lock is no longer needed for MCP notifications.

shopware-entity-delete now accepts mapping entities such as product_category, so an MCP client can remove a product from a category, or a property option from a product, without deleting either side. ids is then a list of objects that name every key field, for example [{"productId": "…", "categoryId": "…"}]. shopware-entity-schema reports the mappingEntity of each many-to-many association. Deleting rows that don't exist is now reported: as an error when none of them existed, and in _meta.notFound when only some did.

Stored MCP tool results are removed after 24 hours

MCP tool results larger than 100 KB are stored in mcp_tool_result_cache and read back by the client with resources/read. Until now they were only removed when the client ended its session with DELETE /api/_mcp or DELETE /store-api/_mcp, so results of clients that never sent it stayed in the database. The new hourly scheduled task mcp_tool_result_cache.cleanup removes results older than 24 hours.

Stored results can contain customer and order data. To keep them shorter, set shopware.mcp.tool_result_cache_ttl (in seconds, at least 60). The cleanup only runs where scheduled tasks are consumed, see Scheduled tasks.

Mail sent and mail error events carry the mail context

MailSentEvent and MailErrorEvent now expose the data a mail was sent with, so subscribers can tell which template, event and sales channel a mail belongs to:

  • MailSentEvent: getData() (the mail data, e.g. templateId), getTemplateData(), getMessage(), getEventName(), getTemplateId() and getSalesChannelId()
  • MailErrorEvent: getData(), getMail() (the mail as far as it was built, null if the error happened earlier), getEventName(), getTemplateId() and getSalesChannelId()

Extensions can add their own spatial media types

A media type that implements Shopware\Core\Content\Media\MediaType\SpatialMediaTypeInterface is shown by the spatial viewer instead of as a picture. MediaEntity::isSpatial() checks for it in PHP and in Twig, while MediaEntity::isSpatialObject() still matches GLB files only. SpatialObjectType implements the interface.

Both are experimental and become stable with 6.8.0.

GLB uploads with external references are rejected

GLB files are now validated on upload. A file is rejected with CONTENT__MEDIA_INVALID_FILE if it is not a valid binary glTF 2.0 container or if the uri of a buffer or image points to something other than an embedded data: URI. Self-contained models, which keep their buffers and textures in the binary chunk, are not affected and URLs in other fields such as extras or asset.copyright are still allowed.

ProductCartDataContextHashEvent for state that product cart data depends on

The data context hash of the cart products only covers the sales channel context and the tax rules. The cart reloads product data only when this hash changes, so extensions whose product data or prices depend on other state get stale cart data. Listeners can add parts to this event to include additional state in the hash for every state outside the sales channel context to force a reload of product data when that state changes.

Apps without permissions can't declare required privileges for MCP tools

An app's MCP tool may only list <required-privileges> in mcp.xml that the app declares in the <permissions> of its manifest, because the tool's script runs with the app's integration role. Apps without a <permissions> block skipped this check, although they have no privileges at all. They are now checked like every other app, so such an app fails to install or update with the message naming the missing permission. Add the permissions to the manifest, or remove the required privileges from the tool.

AssertValuesTrait deprecated in favour of ArrayValues

The test helper trait \Shopware\Core\Framework\Test\Api\Serializer\AssertValuesTrait is deprecated and will be removed in 6.8. Its assertValues() method now triggers a deprecation, and throws when the v6.8.0.0 flag is active. Extension tests that use the trait should call \Shopware\Core\Test\Assert\ArrayValues::assertValues($expected, $actual) instead; the comparison is unchanged.

PromotionItemBuilder::buildPlaceholderItem() is deprecated

PromotionItemBuilder::buildPlaceholderItem() is deprecated and will be removed in 6.8.0.0, because PromotionLineItemFactory::create() builds the same promotion placeholder line item. Create the placeholder through the LineItemFactoryRegistry with the promotion type instead, as the Storefront and the order recalculation now do:

:::php
$lineItem = $lineItemFactoryRegistry->create([
    'type' => LineItem::PROMOTION_LINE_ITEM_TYPE,
    'referencedId' => $code,
], $salesChannelContext);

Public constant for the MCP connect-URL toolset selection

McpToolsetRegistry::QUERY_PARAMETER now names the toolsets query parameter that selects toolsets in the MCP connect URL, on /api/_mcp and /store-api/_mcp. The parameter exists since 6.7.15.0, the constant since 6.7.16.0. Code that also supports 6.7.15 detects the parameter with \defined(McpToolsetRegistry::class . '::ALL_TOOLSETS') and uses the literal 'toolsets'. Do not probe the internal McpRequestedToolsetResolver class, which can be renamed without notice.

shopware-toolsets-list now reports the toolsets selected in the connect URL as enabled. A tool group named all now belongs to the other toolset, because ?toolsets=all selects every toolset. McpToolsetRegistry::advertisedTools() resolves all the same way as advertisedToolsForNames().

API

MCP tool results carry structuredContent and isError

Tool calls on /api/_mcp and /store-api/_mcp now return the result data as structuredContent next to the existing text block, so clients can read it without parsing JSON out of the text. Clients that negotiate a protocol revision before 2025-06-18 get no structuredContent, because that revision introduced it. A failed call sets isError: true and puts a stable code in structuredContent.error.code, for example missing_privilege, invalid_arguments or tool_error. The error envelope in the text block gains the same code key. Every result also carries _meta["shopware/generatedAt"].

The text block is unchanged, so clients that parse {"success": ..., "data": ...} keep working. Plugin tools that extend McpToolResponse get the new fields without any change. Tools can pass a stable code as the second argument of McpToolResponse::error().

With the v6.8.0.0 feature flag, the first text block holds the plain data or the error message instead of the success envelope, followed by the summary and the metadata ({"_meta": …}) as blocks of their own. See UPGRADE-6.8.md for how to switch a client.

A tool result larger than 100 KB is stored and linked instead of returned inline. The link is now a signed pointer for the caller: the integration or user on /api/_mcp, the sales channel and sw-context-token on /store-api/_mcp. It expires after an hour and can be read on any later request of the same caller, with or without an MCP session. Another caller gets "not found".

The result keeps _meta.resourceUri in the text block and adds _meta.expiresAt. Clients on protocol 2025-06-18 or later also get the link as a resource_link content block. The Store API endpoint can now read these results with resources/read; before, its links could not be resolved. Store API clients have to send the same sw-context-token on the read. A Store API call without sw-context-token gets its result inline, because the token minted for that one request could never read it back.

Parents without an available variant are not found

/store-api/product/{productId} now responds with HTTP 404, like for an inactive product, when the requested product is a parent and none of its variants can be shown: all variants are inactive or not visible in the sales channel, or they are out-of-stock closeouts while core.listing.hideCloseoutProductsWhenOutOfStock is active. Previously the parent was returned as a product without a buyable variant, so the storefront rendered a detail page that could not be ordered from.

For the same reason, /store-api/product/{productId}/cross-selling no longer returns such a parent in a cross-selling with manually assigned products.

Generated document number in the V2 creation response

The POST /api/_action/order/document-v2/create response now includes documentNumber, allowing clients to compare the assigned number with a previously displayed preview.

HTML in customer name and address fields is rejected with a dedicated violation

Registration and address routes now reject HTML in firstName, lastName, title, company, department, street, additionalAddressLine1, additionalAddressLine2 and city with the violation code VIOLATION::CONTAINS_HTML_ERROR and a source pointer to the offending field. Previously such input was emptied while being sanitized and then surfaced as a generic error that the storefront could not attach to a field, so a first name like <John failed registration with "Something went wrong".

Input that only looks like markup, for example I <3 you or 5 > 3, still passes. The check is available as the reusable constraint Shopware\Core\Framework\Validation\Constraint\NoHtml for your own validation definitions.

A required birthday is enforced by the Store API

When core.loginRegistration.birthdayFieldRequired is active, POST /store-api/account/register and POST /store-api/account/change-profile now reject a request without birthdayDay, birthdayMonth or birthdayYear with a VIOLATION::IS_BLANK_ERROR on the missing field. Previously the customer was saved without a birthday. Headless frontends must send the birthday when the setting is active.

Store API OpenAPI schema matches the actual responses

The Store API OpenAPI schema was corrected where it contradicted the real responses; the responses themselves are unchanged. If you generate types or validate responses from the schema, regenerate them. Notable changes:

  • aggregations, Cart.errors, paymentChangeable, validationData and OrderLineItem.translated allow an empty array; order price calculatedTaxes/taxRules and CmsSlot.fieldConfig are arrays.
  • OrderLineItem.payload can be an empty array; its options are { group, option } pairs, its dates use the storage format Y-m-d H:i:s.v, and its ID lists can be null. PropertyGroupOption no longer declares option or requires group.
  • Country.addressFormat and currentFilters.navigationId are no longer required, and redirectUrl can be null.
  • POST /product/{productId}/review and GET /breadcrumb/{id} document their 204 responses.

Store API reads every field of the compressed _criteria parameter

On every Store API GET route, _criteria is now a compressed form of the query string. A GET request with _criteria reads the same fields as the POST request with that body, for example limit, includes, slots, depth or options. Before, many routes ignored these fields inside _criteria. Fields that a route reads from the POST body only, such as the listing filter flags, are still not read.

If you send _criteria, note:

  • A field in _criteria wins over a query parameter of the same name. Other query parameters are applied next to it, also on the Admin API.
  • Values are read as strings, as in a query string, also by listing filters of extensions. null counts as not sent.
  • The sw-include-search-info header is respected together with _criteria.
  • An invalid _criteria value is answered with 400 on every Store API GET route.

On the Store API, includes and excludes that are not an array are answered with 400 instead of 500.

The OpenAPI schema declares _criteria on more GET operations and no longer marks search of searchSuggestGet, ids[] of readMediaGet and options[] of searchProductVariantIdsGet as required.

Store API schema describes line item payloads per type

The Store API schema no longer restricts the line item type to the core values, because extensions add their own types such as customized-products or subscriptionDiscount. payload is an open object whose keys depend on type. The new ProductLineItemPayload and PromotionLineItemPayload schemas describe the payloads of product and promotion line items, and LineItem and OrderLineItem apply them through if/then conditions on type. Both mark every key core always writes as required, except productType and composition. An order keeps the payload of the version that placed it, so orders from older versions can lack keys added since. The cart LineItem.payload no longer references ProductJsonApi. That schema was removed, along with the JSON:API resource and relationship schemas that only it used.

Show a line item whose type you don't know from its common fields, such as label, quantity and the price, and ignore its payload. If you generate types from the schema, cast payload to ProductLineItemPayload or PromotionLineItemPayload after checking type. Extensions that add a line item type can describe its payload the same way by adding an if/then entry to allOf of LineItem and OrderLineItem in their own Store API schema files. Keys an extension adds to a core payload go into the properties of ProductLineItemPayload or PromotionLineItemPayload in those files. Undeclared keys aren't part of the schema, so generated types don't contain them.

Store API resolves the context from the storefront session on request

A Store API request that sends the storefront session cookie together with sw-access-key and the new header sw-context-source: session is resolved with the context token held in that session, so a client embedded in a storefront page shares the shopper's cart and login without managing a token. Login, registration, logout and password changes made this way are written back into the session.

The session is resumed, never created, so this only works alongside a storefront. When it cannot be used the request fails with FRAMEWORK__ROUTING_SESSION_CONTEXT_NOT_RESOLVABLE (HTTP 400) instead of falling back to a new context, for example without a session cookie, when sw-context-token is sent alongside, or when the session holds no token for the sales channel. Requests without the header are unaffected. Deployments that widen the default CORS configuration must keep sw-context-source out of the allowed headers.

Session-resolved responses carry no sw-context-token header. The HTTP cache treats these requests like any other, keyed by the sw-cache-hash cookie they share with the storefront page, so a cacheable route can be answered from the cache without the session being resolved. The responses also keep the storefront's HTTP cache cookies current, so the next storefront page reflects a login or cart change made this way.

Regulation price contains the saving

regulationPrice of a calculated price now contains discount and percentage next to price, calculated against the unit price like listPrice. percentage is negative when the unit price is above the regulation price, so only show a saving when it is greater than 0.

Store API responses contain the new properties wherever they contain a regulation price: in calculatedPrice, calculatedPrices and calculatedCheapestPrice of products, for example in the product listing, search and detail responses, and in price of cart and order line items.

Shopware\Core\Checkout\Cart\Price\Struct\RegulationPrice is created with RegulationPrice::createFromUnitPrice($unitPrice, $regulationPrice), which calculates both values. Its constructor becomes private in v6.8.0.

REST API indexing behavior header is honored

The indexing-behavior header now supports use-queue-indexing and disable-indexing on REST API writes, matching the existing Sync API behavior. Requests without this header retain the current synchronous indexing behavior.

Store API payment, shipping and checkout gateway routes accept an orderId

/store-api/payment-method, /store-api/shipping-method and /store-api/checkout/gateway accept an optional orderId (query or body). Combined with onlyAvailable, they evaluate availability for that order instead of the session: the order's currency, language, customer, addresses and stored rule IDs, without re-evaluating rules. The order must be loadable through /store-api/order for the logged-in customer or guest; unknown and foreign orders both answer CHECKOUT__ORDER_ORDER_NOT_FOUND. These responses are not cacheable. POST /store-api/order/payment validates on the same basis.

New Store API route to add the products of an order to the cart

POST /store-api/checkout/cart/reorder/{orderId} adds an order's products to the cart, and the checkout.cart.collect-reorder-line-items extension lets you add or drop items before they reach the cart

Administration

Import/export profile language tooltip deprecated

The snippet key sw-import-export.profile.addNewProfileTooltipLanguage was deprecated and will be removed in 6.8.0 without replacement. Profiles can now be created in any content language, so extensions should remove references to this tooltip.

sw-empty-state stays available until 6.9

The removal of the deprecated sw-empty-state component was moved from 6.8 to 6.9, so extensions using it keep working with 6.8. Development builds log a deprecation warning once per usage site, including the component path that renders it. Migrate to mt-empty-state before 6.9, see UPGRADE-6.9.md.

Native setup components support defineModel()

defineModel() was rejected at build time inside a Shopware <script setup> block, so a base component had to declare the prop and the update: emit of a v-model binding by hand. It is now supported in base components and behaves as in any Vue component — the prop, the emit, the modifiers object and { default } are all Vue's own:

:::vue
<script setup lang="ts">
const modelValue = defineModel<string>();

swDefinePublic({ modelValue });
</script>

The binding is ordinary base state: private unless listed in swDefinePublic({ ... }), and a write to it still emits, from the template, from a parent holding a template ref, and through an override that replaced it.

defineModel() stays rejected in an override component, which declares neither props nor emits. Base components can use Vue's conventional binding names, including const title = defineModel('title') and [modelValue, modelModifiers] = defineModel().

[Internal] Native <sw-block> names are isolated per component

Native <sw-block> blocks are now identified by componentName + blockName, matching how TwigJS identifies a {% block %}. Previously they matched on the block name alone, so a <sw-block extends="foo"> or a legacy Twig override of foo could apply to a <sw-block name="foo"> in an unrelated component. Blocks with the same name in different components are now isolated, and a name / extends pair only resolves against each other within the same component. No action is required from core or plugin developers.

Custom-field set loader computed properties deprecated

The following Administration components now load custom-field sets through customFieldDataProviderService. This replaces their separate loaders with one shared implementation and gives each component cached results by entity, language, and requested limit. Their previous loader computed properties remain available in 6.7 but are deprecated for v6.8.0:

Component Deprecated computed properties
sw-category-detail (categories and landing pages) customFieldSetRepository, customFieldSetCriteria, customFieldSetLandingPageCriteria
sw-customer-detail-base customFieldSetRepository, customFieldSetCriteria
sw-customer-detail-addresses customFieldSetRepository
sw-manufacturer-detail customFieldSetRepository, customFieldSetCriteria
sw-order-detail-details customFieldSetRepository, customFieldSetCriteria
sw-sales-channel-detail customFieldRepository
sw-settings-units-detail customFieldSetRepository, customFieldSetCriteria
sw-bulk-edit-customer, sw-bulk-edit-order, sw-bulk-edit-product customFieldSetRepository, customFieldSetCriteria

Extensions that load renderable custom-field sets should use Shopware.Service('customFieldDataProviderService').getCustomFieldSets(entityName) instead.

The repositoryFactory injection in sw-customer-detail-base remains available only for its deprecated customFieldSetRepository property and will be removed in 6.8.

New extension points for the Shopping Experiences layout list

The "Set as default" context menu item in sw-cms-list is now wrapped in its own Twig block, in both the grid and the list view:

  • sw_cms_list_listing_list_item_option_set_as_default (grid view)
  • sw_cms_list_listing_list_data_grid_actions_set_as_default (list view)

It was previously the only context menu item in either view without a block, so extensions that had to change it were forced to replace the surrounding sw_cms_list_listing_list_item or sw_cms_list_listing_list_data_grid_actions block completely. That removed every other extension point inside those blocks for all other extensions.

In addition, sw-cms-list has a new isDefaultLayout(page) method that decides whether a layout is a default layout. It backs the is-default property of sw-cms-list-item, the label built in getPageType(), and the visibility of the delete action in both views, all of which previously repeated the same check inline. Extensions that add their own default layout type can override this single method instead of the template, and their default layout is then marked and protected from deletion like the built-in ones:

:::js
Shopware.Component.override('sw-cms-list', {
    methods: {
        isDefaultLayout(page) {
            return this.myDefaultLayoutId === page.id || this.$super('isDefaultLayout', page);
        },
    },
});

Together, these two changes remove the need to override the surrounding blocks, so several extensions can add items to the layout context menus at the same time.

$tc stays available until 6.9 and reports its usages

The removal of $tc() was moved from 6.8 to 6.9, so extensions using it keep working with 6.8. $tc(), the root instance $tc(), and Shopware.Snippet.tc() are deprecated in favor of $t() and Shopware.Snippet.t().

In development builds, the Administration now logs a deprecation once per component and snippet key that is translated with $tc(), naming the component and the replacement call. The deprecations use Shopware.Feature.triggerDeprecationOrThrow() with the V6_9_0_0 major flag.

Calls in the vue-i18n 8 argument order $t(key, plural, namedParameters) lost their named parameters since the vue-i18n 10 update. They are now converted to $t(key, namedParameters, plural) and reported with a deprecation warning. The conversion will be removed in 6.9.

The ESLint rule sw-core-rules/no-tc-translation in the Administration extension tooling reports and autofixes both patterns in JavaScript, TypeScript, Vue templates, and bound attributes of Twig templates. {{ }} interpolations in .html.twig templates are not checked and have to be migrated manually. The locale argument of $tc(key, plural, locale) becomes the locale option of $t(key, plural, { locale }). Calls whose named parameters are no object literal or contain spread or computed keys, e.g. $t(key, 1, params) or $t(key, 1, { ...params }), are reported without an autofix. Like the other deprecation rules of the extension tooling, it follows internalApiSeverity:

:::bash
bin/console administration:check-extensions -- --only=MyPlugin --fix

The meta title and meta description fields of the category and product SEO forms show how many characters they contain against the recommended length, and highlight the hint when the recommendation is exceeded.

  • Category: 70 characters for the meta title and 150 for the meta description. The n/255 counter for the stored maximum stays unchanged.
  • Product: 56 characters for the meta title and 155 for the meta description, as named in their help texts. Inherited values are counted as well.

Extensions can customize the hints through the new Twig blocks sw_category_seo_form_meta_title_hint and sw_category_seo_form_meta_description_hint for categories, and sw_product_seo_form_meta_title_hint and sw_product_seo_form_meta_description_hint for products.

Admin list and card empty states use mt-empty-state

The prominent empty states of the Administration render mt-empty-state instead of sw-empty-state, plain text or illustration markup. List pages whose empty state means "nothing exists yet" offer their create action in its button slot, and the customer group, flow and rule lists hide their listing while the empty state shows, so blocks nested inside those listings no longer render.

The Twig blocks that wrapped the former icon, image or label are now empty anchors, deprecated for removal in v6.8.0; pass a custom icon through the icon prop by overriding the surrounding *_empty_state block instead. sw_promotion_v2_individual_codes_behavior_empty_state_actions fills the button slot now, so overrides must switch from <template #actions> to <template #button>, and the former icon and label classes of these empty states no longer exist. The assetFilter computed of these components is deprecated for removal in v6.8.0; use Shopware.Filter.getByName('asset') instead.

The deprecated Twig blocks and computed properties can be found here

Main menu group "Catalogues" is now "Products"

The first main menu group is labelled "Products", its product list entry is labelled "Overview", and the matching group in Settings > Users & permissions is labelled "Products" as well. Menu ids and privilege parent keys are unchanged: entries still hook into the sw-catalogue menu id, and privileges still use parent: 'catalogues'.

The category menu entry moved from position 20 to 25 so that it no longer ties with the reviews entry at 20. Extension entries in the group that relied on the tie order need an explicit position.

Permission groups follow the main navigation

The group order in the permissions grid of Settings > Users & permissions follows the main navigation (Products, Orders, Customers, Content, Marketing, Settings) instead of the alphabetical order of the translated labels, with groups of extensions sorted alphabetically after them and "Other" last.

The order is the parentOrder computed of sw-users-permissions-permissions-grid, and label lookups go through its parentLabel() method; both can be overridden to place an extension's group.

Runtime guards for Administration deprecations

Shopware.Feature.triggerDeprecationOrThrow(majorFlag, message) warns about a deprecated Administration API in development builds and throws once the major flag is active, like its PHP counterpart.

The deprecated option of components and props now follows the same lifecycle: it used to only warn and now throws in next-major mode. See the coding guidelines for usage.

Order line items are paginated

The line item list on the order detail page shows 10 items per page once an order has more than 10 top-level line items. A pagination with an items-per-page selection appears below the list. Searching or adding a line item returns to the first page.

In sw-order-line-items-grid, the orderLineItems computed property still returns all line items that match the search. The grid renders the new paginatedLineItems computed property, and the pagination lives in the new sw_order_line_items_grid_pagination block. Extensions that need the full list keep using orderLineItems.

Order state selects show a status dot

sw-order-state-select-v2 renders an mt-select instead of sw-single-select. The field shows the current state as its value with a status dot, and each option shows the status dot of its target state. The dot color comes from the new optional stateName prop, which takes the technical name of the current state. Without stateName, the field shows no dots and renders the placeholder as the current state in the regular text color, so pass it to get the value and the colors. The state-select event and the sw_order_state_select_v2_field block are unchanged. Styles that targeted sw-single-select elements inside this component no longer apply.

Native-setup build errors point at the author's source

Errors raised by the native-setup transform now carry the line and column of the offending code in the original .vue file and print a code frame, in Vite, Jest and the valid-shopware-setup ESLint rule alike. Syntax errors previously reported block-relative Babel coordinates, and marker or reserved-name errors pointed at the start of the file or block.

The transform now also rejects v-model on a forwarded override binding inside <sw-block extends> content, the same way it rejects count++ or count = 1 there. Such a binding arrives read-only through the slot scope, so the write never took effect. Member writes such as v-model="form.name" remain allowed; mutate override state from a handler defined in the override setup instead.

Import the global Shopware object with shopware:* modules (experimental)

Administration code and extensions can now import selected APIs from the global Shopware object:

:::ts
import { createId } from 'shopware:utils';
import { warn } from 'shopware:utils/debug';
import { Criteria } from 'shopware:data';
import swFormFieldMixin from 'shopware:mixins/sw-form-field';
import useSwOrderDetailStore from 'shopware:stores/swOrderDetail';

This surface is experimental and not covered by the backwards-compatibility promise: the available specifiers, what each one exports, and their types can change in any release without a deprecation cycle. It is annotated @experimental stableVersion:v6.8.0, and becomes stable public API with Shopware 6.8. Shopware.* access is stable, so code that keeps using the global needs no change.

The shopware:utils and shopware:data roots provide named exports. Their subpaths provide default exports, and declared utility namespaces can also provide named exports. Mixins and stores only provide subpaths for Administration registrations. A store subpath returns a composable that resolves the store when called.

Existing Shopware.* access remains supported. Use Shopware.Store.get() and Shopware.Mixin.getByName() for registrations that an extension creates at runtime.

Use the mixin-replacing composables in extensions (experimental)

The composables that replace Administration mixins, such as useListing, useNotification and useValidation, are now available to extensions through Shopware.Composables and the shopware:composables module:

:::ts
import { useListing } from 'shopware:composables';
import useNotification from 'shopware:composables/useNotification';

const { page, limit, total } = useListing({ getList });

Call them in setup() only. They are annotated @experimental stableVersion:v6.9.0, so their names and signatures can change before Shopware 6.9.

An extension that imports shopware:composables requires Shopware 6.7.16.0 or later, so require shopware/administration >=6.7.16.0 in its composer.json. On an older Administration, the import throws an error that names the required and the installed version. An extension that still supports older versions keeps using the mixins.

The SFC migration codemod now imports the composables from shopware:composables, so a migrated extension component looks like a migrated Administration one. In an extension, a component that uses the cms-element mixin is skipped, because its useCmsElementDeprecated replacement is not published; migrate it to useCmsElement by hand.

Mail template trigger event is preselected

The trigger event select in the mail template detail sidebars is now preselected with the event of the active flows sending a template of the selected type, if they all use the same event. Preselection requires the flow:read privilege.

mt-divider and mt-help-text are registered globally

mt-divider and mt-help-text from the Meteor Component Library are now registered globally and can be used in Administration templates.

sw-order-state-select-v2 can render a regular size select

sw-order-state-select-v2 has a new small prop. It defaults to true, so existing usages keep the small select. Pass :small="false" to render a regular size select, for example in a form next to other fields.

Result descriptions for entity selects and rule conditions

sw-entity-single-select and sw-entity-multi-select accept the new props descriptionProperty, descriptionFormatter and descriptionFormatterArgs. They render a description below each search result: the value of descriptionProperty, optionally passed through the Shopware.Filter named in descriptionFormatter, with descriptionFormatterArgs as additional arguments. A new breadcrumb filter joins a breadcrumb with an optional separator, which defaults to /.

Rule conditions using RuleConfig::entitySelectField() can set the same keys in their field config to show a description in the rule builder:

:::php
->entitySelectField('categoryIds', CategoryDefinition::ENTITY_NAME, true, [
    'descriptionProperty' => 'breadcrumb',
    'descriptionFormatter' => 'breadcrumb',
]);

The "Item in category" condition uses this to show the category breadcrumb again.

Meteor Component Library updated to 5.8.0

The Administration now uses Meteor Component Library 5.8.0, Meteor Admin SDK 6.15.0 and Meteor Icon Kit 5.11.0. Autofilled form fields are now readable in dark mode.

Check your Administration extensions for these changes:

  • The global font settings changed, so some glyphs look different and text renders slightly narrower, for example a single-storey a and closed digits.
  • mt-select centers its content, and small selects (size="small") have a height of 32px. Remove custom paddings that only compensated for the previous alignment.
  • The time zone hint of datetime mt-datepicker fields is rendered by mt-field-hint. Styles targeting .mt-datepicker__hint-icon or .mt-datepicker__hint p no longer apply; data-testid="time-zone-hint" is unchanged.
  • The search input of mt-select gets the field's name, or a generated id, as its id and opts out of browser autofill.
  • Text-entry fields forward the autocomplete attribute to the native input.

Order quantities of digital products can be set in the Administration

The deliverability card of digital products has a new "Allow multiple units per order" switch. Turn it on to set minPurchase, purchaseSteps and maxPurchase, or off to limit the digital product to one unit per order. Bulk edit no longer hides these fields for digital products.

Extensions can add items to the product media grid

sw-product-media-form has new extension points for showing further items between the product media without replacing the whole grid:

  • The computed property galleryItems returns the grid items in display order. The grid and its placeholders are built from it.
  • The Twig block sw_product_media_form_grid_item renders a single grid item, so an override can render its own items and keep {% parent %} for the product media.
  • The method moveGalleryItem(item, position) moves a grid item when it is dragged or marked as cover. Override it together with galleryItems when your items share the position space with the product media.

Snippet set selection for sales channel domains is grouped by language

The snippet set select in the sales channel domain modal now lists all snippet sets, grouped by the languages of the sales channel. The group of the selected domain language comes first, followed by the groups of the other sales channel languages and one group with all remaining sets. Within a language group, sets matching a sales channel locale exactly are listed first. Selecting a language sets the snippet set with the same locale, or the first set of the same language, unless the selected set already belongs to that language.

sw-sales-channel-detail-domains renders the select with sw-grouped-single-select instead of sw-entity-single-select inside the block sw_sales_channel_detail_domains_input_snippet_set. Overrides of that block or of the computed snippetSetCriteria need to be adjusted: the component now loads all snippet sets and locales once in createdComponent and builds the options in the computed snippetSetOptions.

Storefront

Category Twig function removal postponed to 6.9

The deprecated category_url and category_linknewtab Twig functions remain available throughout 6.8 and will be removed in 6.9.0. Extension and theme developers should migrate to category.seoUrl (available on SalesChannelCategoryEntity) and category.shouldOpenInNewTab. See the migration instructions.

Essential characteristics label custom fields with their snippet

Essential characteristics of a product now label a custom field with its customFields.<name> snippet, which Shopware creates in every snippet set when the custom field is created. Editing that snippet in Settings > Snippets, or overriding it in a theme's snippet files, changes the label shown in the cart, the off-canvas cart and the checkout. Before, only the label from the custom field's configuration was used and changes to the snippet had no effect.

The snippet is read in the language of the context, so Store API carts and orders recalculated in the Administration are labelled in their own language as well. The label of the custom field's configuration is still used when the snippet is missing, empty, or holds the technical name of the custom field, which is the value Shopware writes for a snippet set whose language had no label when the custom field was created. Shops that changed the label in Settings > Custom fields without updating the snippet now see the snippet value, which still holds the label from the time the custom field was created. Update the snippet to the new label in this case.

Improved extensibility of buy widget form

Extensibility of the buy-widget-form.html.twig template has been improved for extension developers. It is now possible to extend data-add-to-cart-options and data-quantity-selector-options objects via the respective twig variables addToCartOptions and quantitySelectorOptions. Additionally, the following new blocks have been added to the quantity selector input group:

  • buy_widget_buy_quantity_input_group_legend
  • buy_widget_buy_quantity_input_group_button_minus
  • buy_widget_buy_quantity_input_group_input
  • buy_widget_buy_quantity_input_group_button_plus
  • buy_widget_buy_quantity_input_group_unit

Legacy theme.json translations keep working and can be migrated with a command

Themes that still define label or helpText in their theme.json show their labels in the theme manager again. On theme:refresh, plugin installation and plugin update, Shopware generates the matching administration snippets from those properties into the private filesystem (snippets/administration/<technicalName>/<locale>.json, see "Snippets can be provided through the private filesystem") and loads them as the lowest-priority snippet layer, so snippet files shipped by the theme always win. A warning is logged while a theme relies on the generated snippets. Snippet files shipped with the theme are the recommended way to provide these translations. The labels and helpTexts fields of the theme and theme_translation entities, which carried the legacy translations, are deprecated and will be removed in 6.8.

The new command theme:migrate-translations <technicalName> writes the snippets into Resources/app/administration/src/snippet/<locale>.json of the theme and keeps every snippet the theme already maintains there. Use --strip to also remove the deprecated properties from the theme.json, and --dry-run to preview the result.

Extension component aliases work in the dev server

The unified Storefront component dev server now applies aliases from each extension's vite.components.config.mts only to imports from that extension's resource tree. Extensions can use the same alias name for different module paths, including imports between modules under Resources/app/storefront/src, in development and production builds.

Anonymous index components use their directory name

Asset entry names for index components were not resolved correctly to the directory name and still used "index" in their names. Storefront components using an index.js or index.ts layout are now registered under their directory name, for example Sw:Comp. The former Sw:Comp:index import-map key is no longer generated, including for existing build manifests. If you used data-component="Sw:Comp:index" as a workaround, change it to data-component="Sw:Comp".

New line item reference price block

A new block component_line_item_reference_price has been added to the template storefront/component/line-item/element/total-price.html.twig. This allows easier customization of the already existing reference price display for line items without having to override the entire total price value block.

Cart settings now offer core.cart.showLegalGuaranteeNoticeInline to display the complete localized legal guarantee notice below the checkout terms and conditions. The setting is disabled by default and requires core.cart.showLegalGuaranteeNotice to be enabled. Themes can customize its placement through the page_checkout_confirm_legal_guarantee_notice_inline and page_checkout_confirm_legal_guarantee_notice_inline_bottom blocks.

Unused theme directories are kept for 24 hours after the switch

After a theme recompile, the previously active public/theme/<hash> directory was deleted as soon as its files were older than 24 hours. That age is measured from the compilation, not from the moment the sales channel switched to the new directory, so a theme compiled weeks ago was removed by the next cleanup right after the recompile. Pages still served from an HTTP cache or CDN then referenced CSS and JS files that returned 404.

The theme.delete_files scheduled task now works in two steps. On the first run after a directory became unused, it marks the directory with a .retired file, provided all of its files are older than 24 hours. On a later run, it deletes the directory once that marker is at least 24 hours old. With the daily task interval, an unused directory is therefore removed 24 to 48 hours after the switch, never earlier. A directory that becomes active again, for example via theme:change --no-compile, loses the marker and stays. A directory that a queued compilation is still writing has fresh files and is left alone. Directories from before this change are handled the same way.

theme:compile and theme:change no longer run that cleanup themselves; compiling and cleaning up are separate jobs again. Both commands still accept --no-cleanup, but the option is deprecated, has no effect and will be removed in 6.8.0.0. Drop it from deploy scripts.

Preserve theme assets on S3-compatible storage

Theme compilation now preserves current images and fonts on storage providers that apply deletions asynchronously. Obsolete theme assets are removed only after the replacement files have been uploaded successfully.

Checkout form data is kept in the session storage

The CheckoutCustomerStorage plugin stores the consent checkboxes of the confirm page, terms of service and revocation, together with the customer comment, in the browser's session storage instead of the local storage. They survive the page reloads within a checkout, for example after picking another payment method, but no longer outlive the browsing session they were entered in. The revocation checkbox moves here from FormPreserverPlugin, which no longer persists it.

The new CheckoutCustomerStorageReset plugin drops that data and is bound via data-checkout-customer-storage-reset. It sits on the emptied cart, as both a page and an off-canvas, on the order confirmation page, and on the login page a logout lands on. Themes that replace those templates should keep the attribute, and can add it to any further place that ends a checkout.

The combined checkout.confirmTermsTextModalWithGuarantee snippet was replaced by checkout.confirmTermsTextModal for terms and checkout.confirmLegalGuaranteeNotice for the separate guarantee notice. Update theme overrides accordingly.

Storefront session handling moved to Core

Shopware\Core\Framework\Routing\SessionContextTokenSubscriber now starts the storefront session, keeps its context token and follows token rotations on login, registration, logout and password changes; Shopware\Storefront\Framework\Routing\StorefrontSubscriber no longer handles the session. The sw-sales-channel-id session key is no longer written. With core.systemWideLoginRegistration.isCustomerBoundToSalesChannel enabled, a password change now updates the sales channel bound session token instead of leaving a revoked one behind.

component/privacy-notice.html.twig now shows the same legal guarantee notice paragraph and modal as the checkout confirmation, whenever core.cart.showLegalGuaranteeNotice is enabled and the form requires terms-of-service acceptance (for example the registration form), independent of the core.loginRegistration.requireDataProtectionCheckbox setting.

Reorder resolves its line items from the order

The reorder form no longer posts lineItems, so its two hidden input blocks are empty and deprecated; add or drop reorder items with the checkout.cart.collect-reorder-line-items extension instead

Savings percentage is based on the regulation price

When a regulation price (lowest price of the last 30 days) is set, the storefront calculates the savings percentage against it instead of the list price and no longer renders the crossed-out list price, as required by Art. 6a of Directive 98/6/EC (CJEU C-330/23). The sale price styling and the discount badges follow the same reference, so they are only shown while the unit price is below the regulation price, also for products without a list price. Without a regulation price nothing changes.

The snippet general.listPricePreviously now reads "Lowest price (last 30 days): %price%" instead of "previously %price%", and "Niedrigster Preis (letzte 30 Tage): %price%" instead of "vorher %price%".

If you override buy-widget-price, block-price, price-unit or badges: isListPrice is false while a regulation price is set, the new isRegulationPriceSaving tells whether there is a saving against it, and the regulation price section renders a list-price-percentage element.

Nested GARAN label opens the full label

The nested GARAN label is now a button that shows the full label. On line items it opens a modal loaded from the new route frontend.product.garan-label (template storefront/component/product/garan-label-modal.html.twig); on the product detail page it expands the full label.

If you override component_line_item_garan_label, buy_widget_garan_label_preview or buy_widget_garan_label_full, take over the new button and link markup.

cms-element-image-gallery.html.twig now shows every media for which isSpatial() is true in the spatial viewer, and derives the slider options for spatial slides from its own items instead of page.product.media, so static galleries get them as well. The AR button and the QR code modal are only rendered for spatial objects.

To pass different options to the viewer, override the new blocks element_image_gallery_inner_item_spatial_canvas_viewer, element_image_gallery_inner_single_spatial_canvas_viewer and element_image_gallery_inner_zoom_modal_slider_item_zoom_container_spatial_canvas_viewer. To build the viewer from something other than a model URL, override SpatialBaseViewerPlugin::createQuickView(); returning null leaves the canvas empty.

The zoom modal no longer waits for images without a source before it opens.

These additions are experimental and become stable with 6.8.0.

App system

App requests keep body and signature across redirects

Shopware now follows a 301 or 302 from an app endpoint without dropping the POST method, the request body or the shopware-shop-signature header, so the redirect target receives the same signed request.

SEO URLs for app storefront routes

Apps can give their script-rendered storefront pages SEO URLs by declaring <seo-url> and <entity-seo-url> elements inside <storefront> in manifest.xml.

:::xml
<storefront>
    <seo-url name="imprint">
        <path>imprint</path>
        <path lang="de-DE">impressum</path>
    </seo-url>
    <entity-seo-url name="blog-detail" entity="ce_blog">
        <default-template>blog/{{ ceBlog.translated.title }}</default-template>
    </entity-seo-url>
</storefront>

A <seo-url> maps its path to the script hook storefront-<name> on every storefront sales channel domain, using the path of the domain's language; the hook attribute overrides the hook name. Installing or updating an app fails when one of its paths is already used by a storefront route, another app or an existing SEO URL.

An <entity-seo-url> generates one SEO URL per entity. The app needs read permission for the entity and for every association its default template uses, otherwise the install or update fails; its own custom entities are covered automatically. A script hook can be claimed by one app only. Its default template is only seeded: merchants adjust it per sales channel in Settings > SEO, where the route is listed as storefront.app.<appName>.<name>, and app updates don't overwrite it. Changing the entity of a route resets all of its templates, including sales channel overrides. The template context exposes the entity under its camel-cased name, for example ceBlog. The URLs follow entity writes and the indexing behaviour like the core SEO URLs, get rebuilt by dal:refresh:index, and are marked as deleted while the app is inactive or after it is uninstalled.

The script receives the entity id as hook.query.id. Templates link to such pages with seoUrl('frontend.script_endpoint', { hook: 'blog-detail', id: entity.id }); the placeholder is replaced with the SEO path like for products and categories.

Four supporting changes apply to all SEO URLs: query parameters stored in seo_url.path_info are merged into the request when the SEO URL is resolved and take precedence over the browser's query string, seo_url.route_name now allows 255 characters, updating the SEO URLs of one route no longer marks the SEO URLs of other routes for the same entity as deleted or restores them, and the SEO URLs of an entity follow a changed technical target (path_info) without losing merchant-edited paths. Editing a canonical URL via PATCH /api/_action/seo-url/canonical now keeps the requested route when several storefront routes serve one entity.

App translations fall back to the closest language

If an app doesn't provide a translation for the shop's default language, Shopware now uses the closest one the app provides: the main region of the same language (de-DE for de-AT), then any other region of that language (en-GB for en-US), then en-GB, then the first translation.

This applies to all translated app texts, including flow actions and their configuration fields, Administration modules, custom fields, rule conditions and document types. Installing an app no longer fails when its flow.xml has no label in the shop's default language.

App events are only delivered to the app they are about

The app events app.installed, app.updated, app.activated, app.deactivated, app.deleted, app.permissions.updated and app.config.changed are now only delivered to app webhooks. Webhooks created through the Admin API no longer receive them. Apps keep subscribing to them in their manifest, as before.

App MCP capabilities use generic feature storage

App-declared MCP tools, prompts, and resources are now persisted through the generic app feature storage. Installed apps do not need to change their mcp.xml, but their existing MCP tools, prompts, and resources are not migrated. Reinstall or update affected apps after upgrading to re-register them.

The dedicated app MCP DAL aggregates (app_mcp_tool, app_mcp_prompt, app_mcp_resource and their translation tables) and their accessors on AppEntity and LanguageEntity were experimental and have been removed.

What's Changed

  • feat(core): migrate app MCP capabilities to app feature storage by @AydinHassan [#19010]
  • feat(sales-channel): Grouped snippet sets in domain selection by @marcelbrode [#21706]
  • feat(administration): allow digital products without uploaded files by @nguyenquocdaile [#21491]
  • feat(checkout): add store-api route to reorder an order by @untilu29 [#20713]
  • feat(administration): show product number in product stream multi-select & style updates by @gecolay [#21365]
  • feat: telemetry improvements by @h1k3r [#21444]
  • feat(checkout): move the open transaction to the new total on order recalculation by @mstegmeyer [#8418]
  • feat: improve plugin create command by @MalteJanz [#20615]
  • feat(framework): link large MCP tool results with signed resource_link pointers by @BrocksiNet [#20951]
  • feat(core): add event to extend the product cart data context hash by @gecolay [#21363]
  • feat(framework): render MCP tool results through a central result object by @BrocksiNet [#20948]
  • feat(custom-fields): Add snippet usage for storefront custom fields by @marcelbrode [#21550]
  • feat(administration): support defineModel() in native setup blocks by @jleifeld [#20707]
  • feat(core): expose three 6.8 behaviors as subfeatures by @keulinho [#20899]
  • feat(framework): validate deprecated framework callback behavior by @keulinho [#21139]
  • feat(administration): show recommended meta length in product SEO form by @gecolay [#21446]
  • feat(administration): add etag to admin snippets response by @gecolay [#21505]
  • feat: core adjustments for return management polish by @larskemper [#21237]
  • feat(core): add order date rule condition by @DennisGarding [#21407]
  • feat: Spatial scene goes storefront by @feliopterix [#20682]
  • feat(administration): support entity descriptions in rule configuration by @larskemper [#21259]
  • feat(administration): add triggerDeprecationOrThrow for JS deprecations by @gweiermann [#20482]
  • feat: add extendability to product media grid. by @feliopterix [#21441]
  • feat(administration): show recommended meta length in category SEO form by @gecolay [#21360]
  • feat(core): expose more data on MailSentEvent and MailErrorEvent by @gecolay [#21362]
  • feat(administration): persist sendMail property in order state change modal to local storage by @gecolay [#21333]
  • feat(storefront): Improve buy-widget-form extensibility by @lacknere [#21280]
  • feat(core): SEO URLs for app-defined storefront routes by @mstegmeyer [#20305]
  • feat(storefront): open the full GARAN label from the nested label by @socrec [#21310]
  • feat(inventory): attach the producer's guarantee terms to GARAN products by @socrec [#21151]
  • feat(administration): configure order quantities for digital products by @rittou [#20938]
  • feat: Warn when product export is only generated by scheduler (#18499) by @Schrank [#18502]
  • feat(administration): enable Meteor text editor with 6.8 flag by @keulinho [#21398]
  • feat(themes): Improve migration path of theme translations by @marcelbrode [#21166]
  • feat: add glb content validation. by @feliopterix [#21350]
  • feat(checkout): make deferred cart errors a 6.8 sub-feature by @keulinho [#20971]
  • feat(checkout): evaluate Store API availability against an existing order by @mstegmeyer [#21240]
  • feat(framework): link subfeatures to major flags by @keulinho [#20863]
  • feat(store-api): resolve context token from the storefront session by @mstegmeyer [#19634]
  • feat(administration): preselect mail template trigger event from flows by @jozsefdamokos [#20905]
  • feat: shim for setup state overrides to options api components by @davidtraum [#20008]
  • feat(administration): shim for sfc override templates to twig templates by @davidtraum [#20006]
  • feat(core): log system activities on a dedicated Monolog channel by @shyim [#21150]
  • feat: Make CompilerPasses internal by @mitelg [#21157]
  • feat(core): let sales channel types declare that they are transactional by @dgrothaus-sw [#21133]
  • feat(administration): migrate extension targets with shopware:composables in the SFC codemod by @jleifeld [#21066]
  • feat: add check to dal:validate to ensure inheritance helper column for associations by @M-arcus [#17314]
  • feat(administration): expose the mixin-replacing composables as Shopware.Composables by @jleifeld [#21060]
  • feat(core): flag feature flag skip guards in unit tests and drop the dead ones by @nfortier-shopware [#20830]
  • feat: polish order detail page by @larskemper [#20959]
  • feat(store-api): publish an extension event from every Store API route by @mstegmeyer [#20978]
  • feat(framework): remove deprecated services when major flags activate by @keulinho [#20787]
  • feat: add product search route extension point by @OliverSkroblin [#17304]
  • feat: add navigation route extension point by @OliverSkroblin [#17308]
  • feat: new account extensions points by @OliverSkroblin [#17296]
  • feat: upgrade dive to version 4.x.x. by @feliopterix [#20970]
  • feat: add system config tabs by @lacknere [#15715]
  • feat(core): flag unreachable assertions after expectException by @nfortier-shopware [#20580]
  • feat(administration): add virtual shopware:* modules for the global Shopware object by @gweiermann [#20246]
  • feat(storefront): add inline legal guarantee notice option by @daniel3010 [#20875]
  • feat: Add PHPStan rule to enforce new Store-API pattern by @mitelg [#20815]
  • feat(core): forbid kernel-booting behaviours in unit tests and unhook the two tests that used them by @nfortier-shopware [#20843]
  • feat(checkout): add order transaction unconfirmed to in-progress transition by @cyl3x [#20654]
  • feat: Add ADR to replace abstract route classes with extension events by @mitelg [#20757]
  • feat: png GARAN label for order confirmation email by @socrec [#20731]
  • feat: add line item reference price block by @lacknere [#20573]
  • feat(administration): add set as default block to cms layout list by @BrocksiNet [#19907]
  • fix(framework): report MCP connect-URL toolsets as enabled (backport: 6.7.16.x) by @dgrothaus-sw [#21834]
  • fix(checkout): render documents when the twig service is decorated by @mstegmeyer [#21682]
  • fix(framework): align MCP allowlist leftovers with explicit selection by @dgrothaus-sw [#21654]
  • fix(core): guard deprecated DAL construction without accessor notices by @keulinho [#21660]
  • fix(store-api): read all fields of the compressed _criteria parameter by @mkucmus [#20967]
  • fix(administration): add catch-all route for Vue router with 404 error page by @davidtraum [#21624]
  • fix(administration): allow profile creation in any content language by @jozsefdamokos [#21742]
  • fix: do not handle a missing list price as ratio 0 in list price rato rule by @DennisGarding [#21686]
  • fix(administration): keep keyboard selection in select result lists by @M-arcus [#21090]
  • fix: allow checkout source rules in shipping and payment assignments by @jozsefdamokos [#21623]
  • fix(framework): build SSO login URLs from trusted origin by @umutdogan4291 [#21523]
  • fix(framework): keep the Twig escape cache with the escaper property Twig 3.30 compiles by @nfortier-shopware [#20907]
  • fix(administration): bump handlebars to 4.7.10 to resolve audit advisories by @patzick [#21681]
  • fix(storefront): propagate theme saves through all inheritance levels by @larsbo [#21641]
  • fix(administration): use configured labels in product streams by @sydinh [#21617]
  • fix(media): apply CSP to static HTML and SVG assets by @keulinho [#21659]
  • fix(administration): hide the currency suffix in product price grids by @M-arcus [#21293]
  • fix(core): anchor end-of-string regex patterns with the D modifier (discovery, checkout, framework) by @nfortier-shopware [#21591]
  • fix(administration): postpone sw-empty-state removal to 6.9 and report deprecated usages by @davidtraum [#21526]
  • fix(administration): deprecate the snippets property of admin modules by @gecolay [#21500]
  • fix: lowercase the app integration id from the request header by @AydinHassan [#21636]
  • fix: revert cms inheritance with system default language by @lacknere [#21630]
  • fix: Custom field type swap by @marcelbrode [#21602]
  • fix(core): require a rating when saving a product review by @M-arcus [#20934]
  • fix: scope the address validator cache per sales channel and reset the cache tag collector by @BrocksiNet [#21172]
  • fix(checkout): add VAT ID constraints before customer.create validation event by @trimethylpentan [#21019]
  • fix(framework): notify MCP list changes through versions instead of a session registry by @BrocksiNet [#21295]
  • fix(store-api): describe line item payloads per type and allow extension types by @mstegmeyer [#21104]
  • fix(framework): let MCP remove many-to-many links through the mapping entity by @BrocksiNet [#20869]
  • fix(storefront): postpone category Twig function removal to 6.9 by @joberthel [#21271]
  • fix(core): anchor end-of-string regex patterns with the D modifier (framework) by @nfortier-shopware [#21177]
  • fix(core): anchor end-of-string regex patterns with the D modifier (inventory) by @nfortier-shopware [#21181]
  • fix: Allow usage of arrow functions in extension events of store API routes by @mitelg [#21599]
  • fix(storefront): keep the GARAN label an order was placed within its lineitem payload by @socrec [#21451]
  • fix(framework): offload large MCP results for every tool, and only where they can be read by @BrocksiNet [#20881]
  • fix(administration): skip license checks for ddev.site domains by @shyim [#21387]
  • fix(framework): add TTL cleanup for mcp_tool_result_cache by @BrocksiNet [#20856]
  • fix(checkout): align customer recovery hash flags by @umutdogan4291 [#21525]
  • fix(framework): stop collapsing empty MCP tool schemas instead of repairing them by @BrocksiNet [#20871]
  • fix(administration): add frontend list of undeclared majors for triggerDeprecationOrThrow by @davidtraum [#21564]
  • fix(framework): rate-limit the MCP endpoints with a sliding window by @BrocksiNet [#20855]
  • fix(inventory): keep storefront pages working when a dynamic product group has no usable filters by @rittou [#21109]
  • fix(core): anchor end-of-string regex patterns with the D modifier (after-sales) by @nfortier-shopware [#21182]
  • fix(core): anchor end-of-string regex patterns with the D modifier (discovery) by @nfortier-shopware [#21180]
  • fix(storefront): lint previously excluded Twig templates by @keulinho [#20794]
  • fix(administration): postpone the $tc removal to 6.9 and report deprecated i18n usages by @jleifeld [#20775]
  • fix(core): return 404 for parent products without an active variant by @tamvt [#21439]
  • fix(seo): prevent TypeError when SEO URL template renders array field by @bneumann97 [#21272]
  • fix(core): bound the unused media reference check to one batch of candidates by @nguyenytran [#16215]
  • fix(administration): refactor media select behavior by @gecolay [#21418]
  • fix(administration): stop rule condition row overflowing its container by @larskemper [#21510]
  • fix(administration): use last known locale until session locale is resolved by @gecolay [#21509]
  • fix(administration): report setup transform errors at the author's source location by @jleifeld [#20483]
  • fix(core): dispatch ProductNoLongerAvailableEvent only for products that became unavailable by @tamvt [#21326]
  • fix(administration): show and save the variant listing presentation the storefront uses by @rittou [#21036]
  • fix(framework): reserve the MCP discovery group for the core discovery tools by @BrocksiNet [#20901]
  • fix(administration): reduce db calls for reset-excluded-search-term request by @gecolay [#21498]
  • fix(core): anchor end-of-string regex patterns with the D modifier (checkout) by @nfortier-shopware [#21179]
  • fix(framework): check MCP tool required privileges for apps without permissions by @BrocksiNet [#21507]
  • fix: optimise line item image in order confirmation mail by @lacknere [#21337]
  • fix(administration): keep the import/export activity grid inside its card by @M-arcus [#21294]
  • fix(administration): close module filter dropdown on first search bar focus by @gecolay [#21336]
  • fix(storefront): wrap long words in the product description by @M-arcus [#21298]
  • fix(storefront): let the review rating filter labels wrap by @M-arcus [#21297]
  • fix(storefront): let badges grow with their text by @M-arcus [#21296]
  • fix(administration): reset the canonical product of duplicated variants by @M-arcus [#21292]
  • fix: keep homepage hreflang links working for legacy HreflangLoaderParameter callers by @lunetics [#18900]
  • fix(checkout): explain that promotions need discountable products by @mstegmeyer [#21238]
  • fix: rule builder multi select field layout by @CR0YD [#20908]
  • fix: validate config before allocating number and creating version by @jozsefdamokos [#21345]
  • fix(storefront): Prevent checkout.cart-merged-hint message on empty previous cart by @gecolay [#21386]
  • fix(core): guard order index migrations against MySQL bug 118151 by @M-arcus [#21087]
  • fix: link existing tag in rule builder create acceptance test by @yusufttur [#21379]
  • fix(administration): lock the inherit switch while a variant inherits by @M-arcus [#21290]
  • fix(core): preserve assets during forced installs on delayed-delete storage by @drewmt [#20400]
  • fix(core): restore the default currency in sales channel contexts by @gecolay [#21366]
  • fix(administration): keep bulk edit order status options apart after saving by @vienthuong [#21213]
  • fix(core): normalize association id paths in API aggregations and sortings by @vienthuong [#21218]
  • fix(inventory): update variant availability when the parent's closeout or min. order quantity changes by @rittou [#21153]
  • fix(administration): emit click-search-result and close panels on search result click by @gecolay [#21220]
  • fix(core): omit the allowance reason code on ZUGFeRD discount charges by @M-arcus [#21187]
  • fix(administration): update Meteor Component Library to 5.8.0 by @jleifeld [#21248]
  • fix(elasticsearch): index the product price so it can be sorted by by @vienthuong [#19092]
  • fix(administration): use production config for plugin builds by @shyim [#21268]
  • fix(store-api): add new SEO URL enums by @mdanilowicz [#21309]
  • fix(storefront): preload the CMS video only when it autoplays by @M-arcus [#21289]
  • fix(core): install services only after the first run wizard by @AydinHassan [#21279]
  • fix(core): bridge product stream builder contracts by @keulinho [#20607]
  • fix(administration): allow saving profile without user changeset changes by @gecolay [#21201]
  • fix: change deprecated PUPPETEER_SKIP_CHROMIUM_DOWNLOAD to PUPPETEER_SKIP_DOWNLOAD by @gecolay [#18254]
  • fix: webpack chunk naming by @feliopterix [#21175]
  • fix(core): key the version route requirements to their placeholders by @M-arcus [#21191]
  • fix(administration): add missing module icon colors setting to user detail page by @gecolay [#21203]
  • fix(core): mark property sorting locale as optional by @keulinho [#21254]
  • fix(framework): forward reset() to the wrapped handler in log decorators by @wakqasahmed [#21183]
  • fix(administration): hide checkbox inheritance switch in default scope by @M-arcus [#20924]
  • fix: Support resolveAliases in Storefront dev server by @Phil23 [#21200]
  • fix(core): skip unused remote thumbnail dispatches by @bneumann97 [#19679]
  • fix(framework): resolve nightly npm audit failures by @patzick [#21210]
  • fix(storefront): overflowing cms full-width sections by @tobiasberge [#21115]
  • fix(administration): keep list filters on the back arrow after a detour by @M-arcus [#20925]
  • fix(storefront): restore list price strike-through in product listings by @M-arcus [#20910]
  • fix(administration): offer document creation after deleting the last document by @M-arcus [#21091]
  • fix(snippets): Consistent use of "datenschutz" terms in de by @marcelbrode [#21225]
  • fix(administration): skip unloaded to-many associations in the changeset generator by @jleifeld [#21136]
  • fix: Component index name resolution by @Phil23 [#21163]
  • fix(administration): confirm the deactivation of a rented extension by @M-arcus [#21190]
  • fix(storefront): show an error when guest order credentials match no order by @daniel3010 [#21223]
  • fix(checkout): keep order shipping country on recalculation by @M-arcus [#20741]
  • fix(storefront): wrap long category names in navigation and breadcrumb by @M-arcus [#21083]
  • fix(media): clean up upload temp file and keep encoded path separators by @MartinKrzykawski [#21054]
  • fix(core): let restoreByOrder delegate to the order converter and deprecate it by @mstegmeyer [#20927]
  • fix(administration): drop removed line items from the order grid selection by @mstegmeyer [#20915]
  • fix(framework): roll back the lifecycle handlers when an app activation fails by @mstegmeyer [#20980]
  • fix(storefront): use one German term for the privacy notice by @M-arcus [#21188]
  • fix(storefront): let the address search wrap below the add button by @M-arcus [#21085]
  • fix: honor REST indexing behavior header by @bneumann97 [#19664]
  • fix(administration): make all product stream multi-tag-select values reachable by @DennisGarding [#21168]
  • fix(core): log app lifecycle activities by @shyim [#21198]
  • fix(core): keep grouping for limit 1 when a total count is requested by @M-arcus [#21093]
  • fix(framework): fall back to the closest language for app XML translations by @mstegmeyer [#20977]
  • fix(core): compute the cache hash independently of the request scope by @mstegmeyer [#20709]
  • fix(administration): keep order detail header blocks properly nested by @M-arcus [#21189]
  • fix(administration): step the rule modal priority by whole numbers by @M-arcus [#21094]
  • fix: use small size for country state button by @leduc92 [#20770]
  • fix: base the savings percentage on the regulation price by @rittou [#18738]
  • fix(administration): unify spacing between custom fields by @jleifeld [#21142]
  • fix: calculate relevant exclusions with current cart context, remove redundant isRequirementValid() calls by @janobi [#13970]
  • fix(administration): stabilize payment method layout test with more specific CSS by @joberthel [#21158]
  • fix(core): apply reviewsPerPage in Store API review list by @app/github-actions [#20734]
  • fix(administration): prevent trailing whitespace in product links by @nguyenquocdaile [#21035]
  • fix: use the stored type on line item update by @vintagesucks [#21126]
  • fix: evaluate non-product items in line item rules by @vintagesucks [#20822]
  • fix(core): pass GARAN mail labels as template data instead of a Twig filter by @socrec [#21047]
  • fix(framework): resolve nightly npm audit failures by @patzick [#21127]
  • fix(core): keep message queue statistics failures from stopping workers by @M-arcus [#20996]
  • fix(administration): report setup transform errors in the Vite watcher by @jleifeld [#20689]
  • fix(administration): keep app iframes from blocking sidebar resizing by @M-arcus [#21089]
  • fix(administration): show a dropdown chevron on the category tree field by @M-arcus [#21082]
  • fix(storefront): let the filter offcanvas header grow with larger text by @M-arcus [#21084]
  • fix(administration): pass parameters to update deactivation messages by @M-arcus [#21088]
  • fix(administration): wrap long notification messages by @M-arcus [#21081]
  • fix(framework): make kernel cache hash independent of plugin loader order by @shyim [#21061]
  • fix(storefront): use brand primary for icon-primary and name hover by @M-arcus [#20892]
  • fix(acceptance): stabilize major checkout and category tests by @keulinho [#21018]
  • fix: Soften breaking change for ConfigurationService by @mitelg [#21073]
  • fix(storefront): use click for cookie consent denial by @joberthel [#21021]
  • fix(inventory): show variant inherited GARAN confirmation in the admin by @socrec [#20999]
  • fix(core): roll back a service whose installation failed by @mstegmeyer [#20529]
  • fix(core): show main variant in filtered listings only if it matches the active filters by @tamvt [#20950]
  • fix(administration): allow replacing the last shipping price matrix by @daniel3010 [#21058]
  • fix(storefront): robots.txt selecting a domain from the wrong sales channel (#17735) by @wakqasahmed [#20640]
  • fix(framework): reset the resolved Twig globals between requests by @nfortier-shopware [#21031]
  • fix(themes): start the 24h grace period when a theme directory becomes unused by @marcelbrode [#21016]
  • fix(administration): show translated sales channel name in review by @M-arcus [#20893]
  • fix(administration): upload the document company logo by @M-arcus [#20995]
  • fix(administration): show translated document type names by @M-arcus [#20936]
  • fix(administration): hide empty search shortcut placeholder by @app/github-actions [#20424]
  • fix(framework): accept a value for the scaffolding --entities option by @BrocksiNet [#20969]
  • fix(core): stop dal:validate treating all attribute entities as one by @mstegmeyer [#20832]
  • fix(administration): clear search in sw-many-to-many-assignment-card on clear button click by @jleifeld [#20987]
  • fix(administration): limit seo fields and adjust definitions by @M-arcus [#20928]
  • fix(administration): use registered major flag for legacy rule condition by @keulinho [#20992]
  • fix(administration): disable index update for incomplete selections by @jleifeld [#20754]
  • fix(storefront): return 404 for admin below virtual domain paths by @M-arcus [#20926]
  • fix(framework): preserve explicit empty system config overrides by @keulinho [#20991]
  • fix(framework): validate version-shaped feature flag IDs by @keulinho [#20953]
  • fix(storefront): preserve theme assets on delayed-delete storage by @drewmt [#20667]
  • fix(storefront): center the image placeholder in product boxes by @M-arcus [#20933]
  • fix(administration): recalculate linked net/gross price when the value is 0 by @jleifeld [#20755]
  • fix(inventory): let digital products follow their max. order quantity by @rittou [#20957]
  • fix(inventory): skip product export validation in batch mode (#19635) by @LukasVoeller [#20096]
  • fix(core): enforce a required birthday in the Store API by @M-arcus [#20931]
  • fix(administration): keep and restore default privileges in saved roles by @M-arcus [#20845]
  • fix(administration): add GARAN fields to product bulk edit by @nguyenquocdaile [#20850]
  • fix(administration): open delete modal only for the clicked row by @M-arcus [#20894]
  • fix(core): keep reference price when decoding quantity price definitions by @M-arcus [#20922]
  • fix(administration): reload sales channel menu after deletion by @M-arcus [#20923]
  • fix(storefront): keep review stars from overlapping product names by @M-arcus [#20929]
  • fix(administration): align CMS slot keyboard actions with clicks by @joberthel [#20858]
  • fix(administration): keep cleared cms block margins empty by @M-arcus [#20935]
  • fix(checkout): skip non-applicable fixed price delivery promotions by @untilu29 [#20169]
  • fix(core): reject uuids with a trailing newline by @M-arcus [#20930]
  • fix(administration): allow saving and duplicating new products by @nguyenquocdaile [#20646]
  • fix(deprecation): allow the deprecation PHPStan rule to load outside core by @shyim [#20937]
  • fix(framework): resolve sales-channel scoped limits in system_config rate limiter policy by @pavel-rossinsky [#18720]
  • fix(administration): read slot-text labels for v-for sw-tabs items by @gweiermann [#20206]
  • fix(administration): scope native sw-block names by component by @gweiermann [#20172]
  • fix(administration): prevent duplicate release date inheritance icon by @nguyenquocdaile [#20679]
  • fix: handle mapping conflicts gracefully when creating a language by @wakqasahmed [#20116]
  • fix(administration): share customer (and -address) between order address selection components in order detail page by @hanneswernery [#20212]
  • fix: reset customer order metadata on bulk order deletion by @stephanniewerth [#20800]
  • fix(core): skip the dal:validate version reference check for many-to-many by @mstegmeyer [#20834]
  • fix(framework): sort custom field positions numerically by @keulinho [#20738]
  • fix(core): tokenize search terms with HtmlSanitizer instead of strip_tags by @mstegmeyer [#20637]
  • fix(storefront): show full selected method descriptions by @daniel3010 [#20909]
  • fix(core): stop cascade delete resolution from looping between two entities by @mstegmeyer [#20584]
  • fix(core): sanitize plain-text fields with HTMLPurifier instead of strip_tags by @mstegmeyer [#20636]
  • fix(core): pass tags as a list to Redis invalidator storage by @M-arcus [#20896]
  • fix(api): align Store API request bodies with the actual validation by @mkucmus [#20739]
  • fix(deprecation): validate deprecated decorator delegation by @keulinho [#20672]
  • fix(acceptance): support reworked document actions by @keulinho [#20760]
  • fix: align admin empty states by @fabianhueske [#20266]
  • fix: allow falsy switch and checkbox custom field values in rule builder by @app/github-actions [#20703]
  • fix(administration): declare build-time packages as dependencies by @gweiermann [#20657]
  • fix(storefront): keep the checkout consent across reloads without outliving the session by @mstegmeyer [#20542]
  • fix(storefront): link legal guarantee notice from privacy notices by @fbckr1 [#20579]
  • fix(framework): follow app server redirects by @cyl3x [#20661]
  • fix(framework): stop version discard from deleting the live entity on merge by @umutdogan4291 [#20195]
  • fix(storefront): separate legal guarantee notice by @namdinh-92 [#20488]
  • fix(storefront): use product ID for order Garan labels by @daniel3010 [#20649]
  • fix(checkout): replace dompdf page count placeholder for core and fallback fonts by @AlexBachmann [#20351]
  • fix: Boot the shared test kernel only when it was shut down by @mateuszfl [#19656]
  • fix(core): remove decorator deprecation reasons by @keulinho [#20650]
  • fix(administration): update sw-block content when slot scope changes by @davidtraum [#19852]
  • fix(mcp): document entity tool parameter contracts, stop dropping criteria errors by @BrocksiNet [#20013]
  • fix(framework): restrict the null MCP allowlist bypass to administrator users by @BrocksiNet [#20600]
  • fix(store-api): resolve OpenAPI schema lint warnings by @patzick [#20666]
  • fix(checkout): allow login when a default address is missing by @untilu29 [#20274]
  • fix(content): revoke newsletter opt-in when a customer changes the email address by @untilu29 [#20170]
  • fix(core): encode mixed Struct arrays by @keulinho [#20645]
  • fix(storefront): deprecate country state post route by @keulinho [#20652]
  • fix(api): align Store API OpenAPI schema with actual responses by @mkucmus [#20656]
  • fix(core): use a speaking file name for merged document downloads by @DrauzJu [#20327]
  • fix(currency): translate duplicate ISO code errors by @keulinho [#20596]
  • fix(framework): validate ACL role writes in user scope by @keulinho [#20648]
  • refactor(checkout): deprecate PromotionItemBuilder::buildPlaceholderItem() by @mstegmeyer [#21733]
  • refactor(framework): remove exception deprecation reason exemptions by @keulinho [#21400]
  • refactor(framework): resolve reverse proxy services at runtime by @shyim [#21008]
  • refactor(core): let webhook policies decide who may subscribe to a service event by @AydinHassan [#21234]
  • refactor(framework): honor explicit subfeature opt-outs by @keulinho [#20898]
  • refactor: move document survivors to v2 domain with class aliases by @larskemper [#21024]
  • refactor(core): move webhook delivery checks into policies by @AydinHassan [#21107]
  • refactor(framework): generate attribute-based entities in plugin scaffolding by @app/github-actions [#20781]
  • refactor(installer): remove unused UniqueIdGenerator by @shyim [#20942]
  • refactor(core): remove Storefront dependency from StoreAppLifecycleService by @mstegmeyer [#20723]
  • refactor: rename catalogues menu group to products by @fabianhueske [#20452]
  • refactor(administration): drive the service card status from the API state by @mstegmeyer [#20531]
  • refactor(core): alias moved classes by @keulinho [#20525]

Full Changelog: https://github.com/shopware/shopware/compare/v6.7.15.1...v6.7.16.0

Get in touch

Discuss about decisions, bugs you might stumble upon, etc in our community discord. See you there ;)

Source: README.md, updated 2026-10-22