System requirements
- tested on PHP
8.2,8.4and8.5 - tested on
MySQL 8,MySQL 9.7,MariaDB 11andMariaDB 12
Features
Connect-time toolset selection for the MCP server
MCP toolsets can now be named in the URL an agent connects to, so their tools are advertised on the first tools/list instead of after a shopware-toolset-enable round trip:
https://<shop>/api/_mcp?toolsets=order,media
https://<shop>/api/_mcp?toolsets=all
This is for agents that read tools/list once per connection. It changes visibility only: the MCP allowlist and the assigned role still decide what may be called, and a connection that passes no parameter behaves as before.
Browser login for CLI tools and other public OAuth clients
The Admin API now supports the OAuth 2.0 authorization code grant with PKCE for registered public clients such as CLI tools and native apps. Users sign in to the Administration and approve access in the browser. The client receives access and refresh tokens with the approving user's permissions, without storing the user's password or an integration secret.
Shopware ships the shopware-cli client. Operators can register their own public clients, see the Hosting & Configuration section.
Document generation v2 (experimental)
Shopware ships a new, opt-in implementation of order document generation. It replaces the legacy pipeline, which is marked with the #[ExperimentalReplacement] attribute, will be deprecated with Shopware 6.8 and removed with Shopware 6.9. Enable it with the DOCUMENT_GENERATION_REWORK feature flag. Without the flag, Shopware runs purely on the legacy implementation.
The architecture and all extension points are documented in the Document (v2) concept guide. The coexistence and migration strategy is defined in the migration ADR.
Classes intended to become public API are annotated @experimental stableVersion:v6.8.0 feature:DOCUMENT_GENERATION_REWORK and may change in any release. With Shopware 6.8, v2 becomes the default and the annotated surface becomes the stable public API.
One document, multiple formats
A document type (invoice, cancellation invoice, delivery note, credit note) can produce several formats in a single generation call: HTML, PDF, ZUGFeRD XML, and PDF with embedded ZUGFeRD XML. All formats of one document are rendered from the same data, share the same document number, and are persisted as separate files. Merchants configure per document type which formats are generated.
ZUGFeRD is no longer a document type of its own. It is a file format of the invoice, cancellation invoice, and credit note types. Mail attachments and the archive download include all generated formats by default, Flow Builder mail actions can select specific formats.
Each generation snapshots the order into a dedicated order version. A document always renders the order state at generation time. Generated files receive unique, readable filenames with configurable per-format infixes. Infixes that would give two formats with the same file extension the same filename are rejected on write with the violation code DOCUMENT_BASE_CONFIG_DUPLICATE_FILENAME_INFIX. An empty sales-channel infix inherits the global one, as the prefix and suffix do.
Opting in
The flag switches all Shopware-driven surfaces to v2: the order documents section in the Administration, Flow Builder document actions, mail attachments, bulk edit, and the customer-facing download routes. The legacy APIs stay functional in both flag states and remain a public contract until their removal in 6.9. Merchants can switch back at any time.
Company information moves to basic information
The company data printed on documents (name, address, tax and bank details, logo) moves to a new "Company information" card in Settings > Basic information. It is configured per sales channel, no longer per document type. Existing values are not migrated. When the card is empty for a sales channel, v2 reads the legacy document settings.
Generation now fails with a clear error when name, street, zip code, city, or country is missing, instead of silently producing an incomplete document. This guarantees valid seller data on every document, including ZUGFeRD invoices.
New Admin API routes
The v2 routes are available regardless of the flag state:
POST /api/_action/order/document-v2/createPOST /api/_action/order/document-v2/uploadPOST /api/_action/order/document-v2/previewGET /api/_action/order/document-v2/{documentId}/download/{format}POST /api/_action/order/document-v2/download-archiveGET /api/_action/order/document-v2/available-types
New document lifecycle business events
Two new events give extensions a hook into the document lifecycle without polling or fetching the document to discover its type, number, order and file:
document.generation.completed(Shopware\Core\Checkout\DocumentV2\Event\DocumentGeneratedEvent) is dispatched when a document is generated or uploaded for an order. It exposesdocumentId,orderId,orderVersionId,documentTypeanddocumentNumber.document.generation.deleted(Shopware\Core\Checkout\DocumentV2\Event\DocumentDeletedEvent) is dispatched when a document is deleted, for both legacy and Document V2 documents. It exposesdocumentId,orderId,orderVersionId,documentNumberanddeletedAt.
Both events are selectable as triggers in Flow Builder. document.generation.completed fires for both the legacy document pipeline (Shopware\Core\Checkout\Document\Service\DocumentGenerator::generate() and ::upload()) and the Document V2 pipeline (POST /_action/order/document-v2/create and POST /_action/order/document-v2/upload); document.generation.deleted already covers both, since deletion goes through the shared document entity regardless of which pipeline created it.
Extending document generation with a plugin
Plugins register document types, data providers, and renderers as tagged services: shopware.document_v2.type, shopware.document_v2.provider, and shopware.document_v2.renderer. Twig template overrides keep working. v2 renders the same @Framework/documents/*.html.twig templates.
The legacy extension points (the document.renderer and document_type.renderer tags, the legacy document events, decorators of the legacy DocumentGenerator) are never invoked by the v2 pipeline. Both variants can be registered side by side during the transition. See the extension points guide.
Apps can register document types
Apps register custom document types through the new <documents> manifest block:
<documents>
<document-type>
<identifier>swag_warranty</identifier>
<label>Warranty certificate</label>
<formats>
<format>html</format>
<format>pdf</format>
</formats>
</document-type>
</documents>Shopware seeds a number range per app document type and blocks install or update when the identifier collides with a core type or another app. The new document-generation app script hook runs after the order is loaded and the number is allocated. Apps can enrich the render data and override template blocks via sw_extends.
Storefront and customer account
Customers download v2 documents through the existing storefront and Store API routes. The URLs do not change. The document type's "display in customer account" setting applies to v2 documents as well. The file format is selected via the Accept header as before.
Documents can be persisted without an order reference
The document.orderId and document.orderVersionId fields are now optional. Extensions that read documents directly should not assume every document belongs to an order. Use the order association only when it is available.
Marking of the legacy implementation
The legacy PHP document domain in Shopware\Core\Checkout\Document is superseded by v2 but not deprecated yet. Its classes carry #[ExperimentalReplacement(version: 'v6.9.0', feature: 'DOCUMENT_GENERATION_REWORK', ...)], which stays silent for static analysis. The @deprecated tag:v6.9.0 annotation follows with Shopware 6.8 once v2 is stable. The legacy Administration services and modals and the document_type and document_type_translation entities are deprecated with @deprecated tag:v6.9.0 already. Document types and formats become code-registered strings. Surviving shared classes move into the DocumentV2 namespace with 6.9.
Timeline: 6.7 opt-in, 6.8 default (opt-out), 6.9 legacy implementation and flag removed. Migration steps are in UPGRADE-6.9.md.
Core
New #[ExperimentalReplacement] BC-change attribute
Core classes that are superseded by a feature which is still @experimental are no longer deprecated ahead of time. A @deprecated annotation asks you to migrate now, but an experimental replacement has no backwards-compatibility promise yet. Such classes now carry #[ExperimentalReplacement] from Shopware\Core\Framework\Deprecation\BCChange instead.
Configurator groups can be built from a supplied combination result
ProductConfiguratorLoader::load() loads the variant combinations itself, so a caller that has to narrow which variants may be offered had no way in: it either constructed the loader with its own AbstractAvailableCombinationLoader or rebuilt the group assembly.
ProductConfiguratorLoader::loadFromCombinations(SalesChannelProductEntity $product, AvailableCombinationResult $combinations, SalesChannelContext $context) takes the result as an argument and builds the groups from it. load() is unchanged and now delegates to it after loading the combinations, so existing callers behave exactly as before.
Store API responses vary on sw-include-seo-urls
The sw-include-seo-urls request header adds seoUrls to Store API responses, but it was not part of Vary or of the built-in HTTP cache key. A cached response without seoUrls could be served to a request that asked for them. The header is now listed in HttpCacheVariantHeaders::HEADERS, so it is emitted in Vary and folded into the cache key. Reverse proxies that honor Vary need no change. Setups with a custom cache key should add the header. An empty header value now counts as absent, matching the cache key.
Shopware Services reconcile their full state daily
A service that missed an account login or logout, a consent change, a failed update, or a deactivation during a system update stayed in that state until the next event for it fired. The daily services.install task now completes compatible service updates and repairs activation and permissions of every installed service according to its current requirements, even when no new revision is available. Account-bound services stay active while their permissions follow the account state. Permitted manual deactivation is preserved. A failure in one service no longer prevents the others from being reconciled. No configuration change is required.
Extensions can change the API CORS header lists
The API answers CORS preflight requests with a fixed list of allowed and exposed headers, so a custom request header of an extension was rejected by the browser on cross-origin calls.
An extension can now contribute its own header names by registering a service implementing Shopware\Core\Framework\Api\Cors\CorsHeaderProviderInterface; autoconfigured services are picked up automatically, otherwise tag them with shopware.api.cors_header_provider.
Every provider receives the same Shopware\Core\Framework\Api\Cors\CorsHeaders instance and can add to or remove from Access-Control-Allow-Headers and Access-Control-Expose-Headers, matching header names case-insensitively.
Shopware's own header names are unchanged and are now contributed the same way, by CoreCorsHeaderProvider; it runs first, so a provider with a lower tag priority can remove one of them.
Authorization code grant on the Admin API authorization server
Decorators of ClientRepository or ScopeRepository should handle the new authorization_code grant identifier. Public clients may use only the authorization_code and refresh_token grants; the write scope is granted as for the password grant.
Shopware\Core\Framework\Api\OAuth\Client\ApiClient accepts optional $redirectUris and $grantTypes constructor arguments and exposes supportsGrantType(). Existing constructor calls remain compatible; getRedirectUri() returns an empty array when no redirect URIs are configured.
New method IdSearchResult::getPrimaryKeyData
The new Shopware\Core\Framework\DataAbstractionLayer\Search\IdSearchResult::getPrimaryKeyData() method returns IDs in repository write format.
Single ID lists are formatted like this: list<['id' => $id]>.
Composite primary keys remain unchanged.
E.g: The returned array can then be passed directly to EntityRepository::delete():
$result = $repository->searchIds($criteria, $context);
$repository->delete($result->getPrimaryKeyData(), $context);GARAN guarantee duration is capped at 600 months
product.guaranteeMonths accepted any positive half-year value above 24 months, so a product could carry a 500 year guarantee.
Writes now also have to stay at or below 600 months (50 years) and are otherwise rejected with the existing INVALID_GARAN_GUARANTEE_MONTHS violation.
The Administration's product detail page enforces the same range.
Values already stored above 600 months are untouched and keep rendering their label;
they only have to be corrected the next time that product is written.
GARAN label in the order confirmation mail is sized and sits next to the line item
The GARAN label that 6.7.14.0 added to the order_confirmation_mail template (see "GARAN commercial guarantee label and EU legal guarantee notice") rendered without dimensions on a full width row of its own, so mail clients scaled the SVG data URI up to the width of the mail and cut it off. The label now carries explicit width/height attributes and renders inside the line item's description cell, with a translated alt text instead of an empty one.
As with the original change, a migration re-applies the template only for shops that never edited their order confirmation mail template. If you customized that template and copied the label markup from 6.7.14.0, replace your <tr><td colspan="6"> label row with the markup from src/Core/Migration/Fixtures/mails/order_confirmation_mail/en-html.html.twig.
GARAN label in the order confirmation mail is embedded as an inline PNG
The order confirmation mail now attaches the GARAN label as an inline PNG instead of an SVG data: URI, which Gmail and Outlook do not display. If you customized that template, replace sw_garan_label_nested_uri with the new sw_garan_label_mail filter as shown in src/Core/Migration/Fixtures/mails/order_confirmation_mail/en-html.html.twig.
Primary/replica connections switch back to the replica between requests
When database replicas are configured (DATABASE_REPLICA_*_URL), the connection now keeps the replica connection open next to the primary one and switches back to the replica between HTTP requests and Messenger messages. Previously a request that wrote to the primary pinned the connection to the primary — in long running runtimes (for example FrankenPHP worker mode) for the whole lifetime of the worker, which silently disabled replica reads. A worker that has written to the primary may now hold two open database connections instead of one; add ?keepReplica=0 to the DATABASE_URL to restore the previous behaviour.
State machine transitions resolve deterministically
When a state machine contains multiple transitions with the same action name and source state but different destination states, firing that action now deterministically resolves to the oldest transition instead of an undefined one. Such conflicting transitions are deprecated: resolving or writing them triggers a deprecation notice, and with v6.8.0.0 existing duplicates are removed and new ones are prevented by a unique database constraint. If your extension needs its own destination state, register the transition under its own action name instead of reusing an existing one.
translation:install --all no longer installs pseudo-locales
--all now covers every configured locale except the pseudo-locales. A pseudo-locale such as ach-UG exists for in-context proofreading and translatability audits, not as a language a shop offers, and installing it created an active "Acholi (Pseudo Language)" alongside the real ones.
It stays installable by naming it explicitly, which is how the audits it exists for ask for it:
translation:install --locales=ach-UG
Installations that ran --all before this change and do not want the pseudo-language can remove it in the administration, or through DELETE /api/_action/translation/{locale} to drop its files as well.
system:install dispatches SystemInstallCompletedEvent
Shopware\Core\Framework\Event\SystemInstallCompletedEvent is dispatched after a successful bin/console system:install. The event exposes the CLI Context. Extensions can subscribe to run post-install work.
When Elasticsearch indexing is enabled and the cluster is reachable, the Elasticsearch bundle listens to this event and creates empty storefront indices and aliases. Storefront search after a fresh install no longer fails with index_not_found_exception because the alias is missing. Population stays a later es:index run.
Sitemap generation for headless sales channels
Sitemaps are now generated for headless (API type) sales channels that have a domain flagged as external storefront (introduced in 6.7.14.0, see "SEO URLs for headless sales channels"). This applies to all refresh strategies: the scheduled task and sitemap:generate now include such sales channels, and the live strategy on GET /store-api/sitemap generates their files on request. The <loc> entries point at the external storefront domain and use the headless SEO URL paths; the file URLs returned by GET /store-api/sitemap point at the configured sitemap filesystem (the Shopware host or its CDN), since the external storefront does not serve the files — headless frontends can serve or proxy them from there, or download them via GET /store-api/sitemap/{filePath}.
Headless sales channels without an external storefront domain for the requested language are skipped silently — matching the behavior of the SEO URL generation — instead of failing with CONTENT__INVALID_DOMAIN under the live strategy. Storefront sales channels are unaffected.
Concurrent sitemap generation is skipped gracefully again
sitemap:generate (without --force) no longer aborts with CONTENT__SITEMAP_ALREADY_LOCKED when another process is currently generating the sitemap of the same sales channel and language — the affected channel is skipped with an error message and the command continues, as originally intended. The generation lock throws Shopware\Core\Content\Sitemap\Exception\AlreadyLockedException again (now extending SitemapException, error code and HTTP status 400 unchanged), so existing catch (AlreadyLockedException) blocks — including those in plugins — work as they did before the sitemap exceptions were consolidated into SitemapException.
Customer imports validate customer number patterns
Customer import records whose customerNumber does not match the configured customer number range pattern for the resolved sales channel are now rejected and written to the invalid-records file. Adjust the imported customer numbers or the number range pattern before retrying the import.
Custom number range increment storages can implement AbstractIncrementStorage::increaseToAtLeast() to raise an existing increment state without lowering higher values.
Dynamic product group assignments follow condition changes
Deleting, editing or moving a condition now updates product_stream_mapping and the derived product.streamIds; previously only adding one did, so rules, promotions and product exports could match on removed conditions.
A group left without conditions, or invalid for another reason, now loses its assignments. A product export bound to such a group fails instead of exporting what it matched before.
Longer advanced postal code patterns for countries
country.advancedPostalCodePattern now accepts up to 1024 characters instead of 255, matching defaultPostalCodePattern.
JsonField::addPropertyMapping() for entity extensions
Shopware\Core\Framework\DataAbstractionLayer\Field\JsonField now has addPropertyMapping(). Plugins can call it from EntityExtension::modifyFields() to extend an existing JSON schema, for example to add another entity key to a structured hitCount map. The field collection passed to modifyFields() is keyed by property name, so $collection->get('hitCount') returns the field.
public function modifyFields(FieldCollection $collection): void
{
$hitCount = $collection->get('hitCount');
if (!$hitCount instanceof JsonField) {
return;
}
$hitCount->addPropertyMapping(new JsonField('landing_page', 'landing_page', [
new IntField('maxSuggestCount', 'maxSuggestCount'),
new IntField('maxSearchCount', 'maxSearchCount'),
]));
}App payment method translations are preserved
Installing or updating an app no longer overwrites existing payment method name and description translations. Manifest texts are only applied to languages without a translation.
New event to register product listing sortings at runtime
Shopware\Core\Content\Product\Events\ProductListingCollectSortingEvent is dispatched while the product listing, search and suggest criteria are built, before the requested sorting is resolved. Add a ProductSortingEntity to $event->getSortings() to make it selectable and applicable at runtime:
public static function getSubscribedEvents(): array
{
return [ProductListingCollectSortingEvent::class => 'addSorting'];
}
public function addSorting(ProductListingCollectSortingEvent $event): void
{
$event->getSortings()->add($mySorting);
}Adding a product to an existing order applies line item factory decorators
POST /api/_action/order/{orderId}/product/{productId} now builds the line item through the LineItemFactoryRegistry instead of creating a plain product line item directly, so extensions that decorate a LineItemFactoryInterface are applied when a product is added to an existing order, the same way they already are in the cart. A decorator that returns a different line item type — or a cart collector that replaces the line item with several others — therefore takes effect in the administration order detail page as well.
When the calculation replaces the added line item, or adds further line items next to it, those receive the delivery positions as well. Adding a product that stays a single product line item is unchanged, including its delivery position.
PromotionCartInformationTrait helper methods deprecated
The helper methods \Shopware\Core\Checkout\Promotion\Cart\PromotionCartInformationTrait::{addPromotionNotFoundError,addPromotionNotEligibleError} are deprecated and will be removed in Shopware 6.8, call $cart->addErrors() directly instead:
// Before
$this->addPromotionNotFoundError($code, $cart);
$this->addPromotionNotEligibleError($name, $cart);
// After
$cart->addErrors(new PromotionNotFoundError($code));
$cart->addErrors(new PromotionNotEligibleError($name));Installing translations from files that are already present
translation:install accepts a new --offline option. It creates the languages and snippet sets for translation files that are already on the filesystem, without contacting the translation repository at all — not even for the metadata lookup that normally runs first.
translation:install --offline --locales=es-ES,fr-FR
This completes the pairing with translation:download, which fetches the files without touching the database. Together they cover setups where the two halves happen at different times or in different places: an installation with restricted egress where the files are copied in by hand, or a deployment that fetches them once while building its artifact and then only needs each installation to point at them.
The presence of the files is verified per locale before anything is installed. Locales named with --locales are installed as a unit: if one of them has no files, the command fails and lists every missing locale instead of leaving a language with no translations behind it. --offline --all instead installs every locale that is provisioned and reports the rest, because a locale the translation repository does not offer must not make the command unusable for the others. The metadata store is neither read nor written in this mode, so a later regular translation:install or translation:update behaves exactly as before.
Shopware\Core\System\Snippet\Service\AbstractTranslationLoader gained link() and hasTranslationFiles() for this, and decorators inherit both from the abstract class without being adjusted. Installing now calls download() and link() instead of load(), so a decorator that wraps load() to observe installs has to wrap link() as well; translation:update keeps going through load(). Extensions that listen to TranslationLoadedEvent need no change, because link() dispatches it exactly like load() does.
Installing a translation ensures its language and snippet set again
Installing a translation used to decide from the state of the translation files whether there was anything to do, and stopped when they were up to date — before creating any language or snippet set. A locale whose files were current but whose language record had been removed, for example by a database restore, could therefore not be reinstalled: translation:install reported success without doing anything, and POST /api/_action/translation/install answered 200 with an empty updated list, which the Administration showed as a successful install.
Whether a translation is current and whether it is actually installed are separate questions, and both entry points now answer both. Files are fetched only when the repository has something newer, or when they are missing locally; the language and snippet set are ensured for every requested locale either way.
Two consequences for operators:
translation:installnow exits with a non-zero code when none of the requested locales can be installed — that is, when neither the repository offers them nor the filesystem carries them. Previously it printed "All translations are already up to date." and exited0. Scripts that check the exit code are affected. The install route already answered such a request with an error.- A requested locale that the repository does not offer and that has no files on the filesystem is reported and left out rather than installed as a language without translations.
POST /api/_action/translation/installkeeps reporting those locales in itsunavailablelist, but a locale whose files were provisioned offline no longer appears there, because it can be installed. Itsskippedlist now names the requested locales that were installed without a download, instead of every locale in the local metadata that was not updated.
Product breadcrumbs work with categories hidden from navigation
Product breadcrumbs are generated again when the product's main category — or its only assigned category — is configured with "Hide in navigation". The flag only removes a category from the navigation menus; it no longer prevents the category from serving as the breadcrumb source on product detail pages, in GET /store-api/breadcrumb/{id}, and in product exports. When the breadcrumb category is determined automatically from several assigned categories, visible categories are still preferred over hidden ones. Inactive categories remain excluded.
Order and category tags are versioned
Tag assignments of orders and categories are now part of the entity version. Creating a version copies the existing assignments into it, and reading, filtering or aggregating tags returns the assignments of the version in the context instead of the live ones. Assignments made in a version reach the live entity on merge and are dropped when the version is discarded.
The tag association routes and a nested tags payload on the order or category both write the mapping for the version in the request context. Writing order_tag or category_tag rows directly assigns the live version unless the payload carries orderVersionId or categoryVersionId.
An already ordered cart cannot be ordered a second time
POST /store-api/checkout/order re-checks inside its cart lock whether the cart is still stored, and answers 404 CHECKOUT__CART_TOKEN_NOT_FOUND when it is not. Two overlapping submits of the same cart — two browser tabs on the checkout confirm page, a retried request — previously produced two orders whenever the second request had loaded its cart before the first one deleted it, because that stale cart still passed the cart hash check.
Shopware\Core\Checkout\Cart\AbstractCartPersister gained exists() for this. The abstract class carries a default implementation that delegates to the decorated persister, so existing implementations keep working, but the method becomes abstract with 6.8.0.0 — implement it in every cart persister of yours before upgrading.
Storefront snippets of apps are served from a persisted snapshot
Storefront snippet files (Resources/snippet/storefront.*.json) shipped by an app are written to the translation filesystem on install and update, and removed on uninstall. A snippet catalogue build reads them from there instead of from the app's location, so a self-managed app's source is no longer downloaded during a storefront request.
Changed snippets of an app reach the storefront on update: raise the manifest version and run app:refresh (or app:update). Apps installed before this release are written to the snapshot the first time their snippets are requested, which reads the app source once.
MailService renders mails with the snippets of their sales channel
MailService now configures the translator for the mail's sales channel while it renders the subject and content. Previously the Flow Builder mail action and SendMailTemplate did this before calling it; now it applies to every mail sent through MailService.
If you replace AbstractMailService without calling the decorated service, configure the translator in your implementation with AbstractTranslator::injectSettings() and resetInjection(). Because the settings only apply during rendering, listeners of FlowSendMailActionEvent and MailBeforeValidateEvent see the translator's default configuration.
MCP servers are registered declaratively
Shopware runs on symfony/mcp-bundle 0.13 with mcp/sdk 0.8, which register both MCP servers declaratively. The extension tags shopware.mcp.tool, shopware.mcp.prompt, shopware.mcp.resource and their shopware.store_api_mcp.* equivalents are unchanged, so plugins and apps that register tools, prompts, or resources need no adjustment. Code that integrates with the MCP internals has to be updated; those classes are marked @experimental stableVersion:v6.8.0. The motivation, the considered alternatives, and the consequences are described in MCP capability registration via the container.
The bundle registers one set of services per server, so the flat service IDs are gone:
| Before | After (Admin API) | After (Store API) |
|---|---|---|
mcp.server
| mcp.server.admin
| mcp.server.store_api
|
mcp.server.builder
| mcp.server.admin.builder
| mcp.server.store_api.builder
|
mcp.registry
| mcp.server.admin.registry
| mcp.server.store_api.registry
|
mcp.session.store
| mcp.server.admin.session.store
| mcp.server.store_api.session.store
|
The hand-built mcp.store_api.registry, mcp.store_api.server.builder and mcp.store_api.server services were removed, as was StoreApiMcpServerBuilderCompilerPass.
Protocol request and notification handlers are scoped per server. Use mcp.admin.request_handler instead of mcp.request_handler, and mcp.admin.notification_handler instead of mcp.notification_handler, to target the Admin API server. The Store API tags mcp.store_api.request_handler and mcp.store_api.notification_handler are unchanged. A handler on the bundle's global mcp.request_handler tag reaches neither server.
The discovery.scan_dirs option of the mcp extension was removed. Capabilities are registered from their DI tag at compile time, so an in-tree bundle capability needs no directory listing. Each server declares the namespace prefixes it exposes under mcp.servers.<name>.registry, and a capability whose namespace no server names is not registered. Run bin/console debug:mcp --native to list capabilities that ended up assigned to no server. Capabilities of plugins and third-party bundles are assigned by McpToolDiscoveryCompilerPass instead of by prefix.
The bundle's mcp.pagination_limit parameter was removed. Shopware sets both servers from its own shopware.mcp.pagination_limit parameter (default 50).
The MCP bundle ships a debug:mcp command of its own. Shopware keeps that name for its command and moves the bundle's command to debug:mcp:native, also reachable as bin/console debug:mcp --native.
Each server owns its own session store, defaulting to %kernel.cache_dir%/mcp-sessions/<server>. Store API MCP sessions that existed before the update are not carried over, so clients re-initialize once.
Both endpoints stay pinned to the protocol revision they served before, so the negotiated protocolVersion and the Mcp-Session-Id behaviour are unchanged.
API
OAuth authorization endpoint
GET /api/oauth/authorizestarts the authorization code flow. It validatesresponse_type=code,client_id,redirect_uri,code_challengeandcode_challenge_method=S256and redirects the browser to the consent page of the Administration. Errors are only redirected to a redirect URI registered for the client; otherwise a JSON error is returned.GET /api/oauth/authorize/infoandPOST /api/oauth/authorizeare used by the consent page and require authentication. Approval additionally requires an access token associated with an admin user. ThePOSTroute returns{ "redirectUri": … }containing the authorization code, orerror=access_deniedwhen the user declined.POST /api/oauth/tokenacceptsgrant_type=authorization_codewithclient_id,code,redirect_uriandcode_verifier. Codes are single use. Refreshing works withgrant_type=refresh_tokenand the sameclient_id; refresh tokens rotate, and reusing an old token revokes its token family. The OpenAPI schema lists the new routes and theauthorizationCodesecurity flow.- An unregistered redirect URI on the authorization, consent-info, or approval endpoint returns HTTP 400 with error code
FRAMEWORK__OAUTH_INVALID_REDIRECT_URIand a readabledetailmessage. No redirect is performed for these errors.
Store API currency headers validate sales channel availability
Store API requests that supply sw-currency-id now reject currencies that are not available on the requested sales channel.
Stale persisted sales channel context options are recovered
When a sales channel no longer provides the language or currency saved for a context token, Store API and storefront requests now remove that stale saved option and continue with the sales channel default. Explicitly requested unavailable languages and currencies still return their existing errors.
Store API context token response header is restricted on cacheable reads
Store API responses no longer echo the request sw-context-token header on cacheable reads when CACHE_REWORK or v6.8.0.0 is active. The response header is returned by endpoints that provide or bootstrap shopper state, for example reading or switching context, login, logout, registration, password change, guest-order login, adding cart items, and context gateway login/register commands. Clients should keep using their existing token unless a response explicitly provides a sw-context-token.
The Store API cart route no longer loads the cart twice
GET|POST /store-api/checkout/cart returns the cart that the sales channel context resolution already loaded and calculated for the context token, instead of reading and calculating it a second time. CartLoadedEvent is therefore dispatched once per request instead of twice, and the cart processors run once. The response itself is unchanged.
Shopware\Core\Checkout\Cart\SalesChannel\CartLoadRoute::load() takes the cart as an optional third argument for this, filled by the CartValueResolver like on the other cart routes. Calling the route without a cart, or with a token that differs from the passed cart, still reads from the cart storage.
AbstractCartLoadRoute::load() is unchanged for now, so decorations keep working, but the parameter is added there in 6.8. Add it to your own load() declaration and forward it to the decorated route before you upgrade. Until you do, the route reads and calculates the cart again behind your decoration, because the resolver has no parameter to fill.
Sales channel file routes require read access
The list, detail and preview routes under /api/_action/sales-channel-file/{fileFamily}/{salesChannelId} now require sales_channel_file:read. Clients with that privilege receive previews using the saved template overrides; supplying unsaved templateOverrides additionally requires sales_channel_file:update.
Resolving the sales channel context now calculates the cart through CartCalculator rather than the CartRuleLoader underneath it. The cart that CartService holds for the current request, and with it every cart the CartValueResolver hands to a controller, therefore carries the context hash of its current state instead of the one stored at its last persist, and its line items are no longer flagged as modified. Read Cart::getHash() if you compare a cart against /store-api/checkout/order; a cart calculation is also measured once per request now, where the context resolution used to calculate without emitting cart.calculation.duration.
Dedicated error code for invalid child line item quantity
CartException::invalidChildQuantity() now returns the error code CHECKOUT__CART_INVALID_CHILD_LINE_ITEM_QUANTITY (constant CartException::CART_INVALID_CHILD_LINE_ITEM_QUANTITY_CODE) instead of reusing CHECKOUT__CART_INVALID_LINE_ITEM_QUANTITY. Previously both invalidChildQuantity() and invalidQuantity() shared the same error code, so the shared storefront message The quantity (%quantity%) is incorrect. was rendered with an empty %quantity% placeholder for the child quantity case (invalidChildQuantity() never provided that parameter). If you match on the previous error code to detect invalid child quantities, switch to the new code.
Headless sales channels return their SEO URLs via sw-include-seo-urls
Store API responses requested with the sw-include-seo-urls header now also include the SEO URLs generated for headless (API type) sales channels. Previously only the storefront SEO URL routes were considered when loading the seoUrls of products, categories and landing pages, so the association stayed empty on headless sales channels even though SEO URLs had been generated for them (see "SEO URLs for headless sales channels" in 6.7.14.0). Storefront sales channels are unaffected.
Remote media request timeouts are configurable
Installations can configure shopware.media.url_upload_timeout and
shopware.media.external_link_timeout in seconds to bound remote media URL
uploads and external-media link checks. Both values default to 0.0, which
preserves the previous unlimited behavior.
Administration
An empty string can be saved on fields that allow one
The changeset generator turned an empty string into null for every field. Fields flagged Required and AllowEmptyString reject null but accept an empty string, so clearing such a field in the Administration always failed with "This value should not be null." The generator now keeps the empty string for exactly those fields; every other field is unchanged.
The entity validation service follows the same rule and no longer reports an empty string on such a field as missing. This affects snippet.value and app_administration_snippet.value, where clearing the field now saves an empty value instead of returning an error.
Update wizard recommends Shopware CLI
The administration update wizard now asks you to choose an update method before starting the web installer. shopware-cli project upgrade is the recommended path for developers and managed deployments. The existing web installer flow remains available.
On cluster setups (shopware.deployment.cluster_setup: true) the web installer is no longer offered: the update button in the wizard is disabled with a hint towards Shopware CLI, and GET /api/_action/update/download-recovery responds with 403 (FRAMEWORK__UPDATE_CLUSTER_SETUP_NOT_SUPPORTED).
Update module can be hidden from the Administration
Operators who manage updates through Shopware CLI or their deployment pipeline can now remove the update module from the Administration entirely. Set shopware.auto_update.hide_module: true or the environment variable SHOPWARE_AUTO_UPDATE_HIDE_MODULE=1 and the module is no longer registered: the "Shopware updates" settings item and its wizard route do not exist, and the update-available notification is suppressed. The flag is also exposed to API consumers as settings.hideUpdateModule in GET /api/_info/config.
The update API endpoints enforce both flags server-side: all GET /api/_action/update/* endpoints respond with 403 (FRAMEWORK__UPDATE_MODULE_HIDDEN) while the module is hidden, and the mutating download-recovery and deactivate-plugins actions respond with 403 (FRAMEWORK__AUTO_UPDATE_DISABLED) while shopware.auto_update.enabled is false.
Consent page for OAuth clients
The new route #/oauth/authorize renders a standalone consent page showing which client wants access to the shop as which user, with Approve and Deny buttons. Logged-out users are sent through the login first and return to the consent page afterwards. The page is backed by the new oauthAuthorizeApiService.
Order drafts are cleaned up when leaving the detail page
Reloading or leaving an order detail page now reliably removes the temporary order version created by the Administration. This prevents unused order versions from accumulating; no action is required.
Optional order confirmation mail for Administration-created orders
When creating an order in the Administration, the options step now includes a "Send order confirmation email to customer" switch. It is enabled by default to preserve the existing behavior; clearing it creates the order normally without sending the order confirmation mail for that order. Storefront checkout behavior is unchanged.
Shipping prices can be linked to the tax rate
The shipping price matrix now renders sw-price-field per currency instead of two separate number fields. Gross and net can be linked with the lock button, and a linked net price is calculated from the gross price using the shipping method's tax rate. New shipping prices are linked by default; existing ones keep their stored state.
Extensions that override the sw_settings_shipping_price_matrix_price_grid_currencies_list block or style the removed .sw-settings-shipping-price-matrix__price-input class must be adjusted to the sw-price-field markup. The gross and net input name attributes are unchanged.
Admin UI shell rework (sidebar, top bar, smart bar)
The Administration shell — main menu sidebar, top bar, search bar, and smart bar — has been modernized and improved in behavior and responsiveness. Extensions that override these areas via Twig blocks, style them via the removed CSS classes, or rely on the previous color props need to adapt.
Removed Twig blocks
The following blocks have been removed and can no longer be extended:
src/app/component/structure/sw-admin-menu/sw-admin-menu.html.twigsw_admin_menu_toggle_sidebarsw_admin_menu_toggle_sidebar_iconsw_admin_menu_toggle_sidebar_textsw_admin_menu_user_actionssw_admin_menu_user_actions_labelsw_admin_menu_user_actions_list
src/app/component/structure/sw-admin-menu-item/sw-admin-menu-item.html.twigsw_admin_menu_item_arrow_indicato(sic)sw_admin_menu_item_arrow_indicatorsw_admin_menu_item_external_arrow_indicato(sic)sw_admin_menu_item_external_iconsw_admin_menu_item_external_text
src/app/component/base/sw-version/sw-version.html.twigsw_version_namesw_version_name_textsw_version_statussw_version_status_badge
src/app/component/structure/sw-search-bar/sw-search-bar.html.twigsw_search_bar_version_display
src/module/sw-sales-channel/component/structure/sw-sales-channel-menu/sw-sales-channel-menu.html.twigsw_sales_channel_menu_context_button_collapsed
Repurposed block: sw_admin_menu_user_actions_items
The user menu in the sidebar footer became an mt-action-menu dropdown. The block sw_admin_menu_user_actions_items still exists, but its content now renders inside mt-action-menu instead of a <ul> navigation list. Overrides that add <li><router-link> entries produce broken markup inside the dropdown and need to render mt-action-menu-group / mt-action-menu-item elements instead:
{% block sw_admin_menu_user_actions_items %}
{% parent %}
<mt-action-menu-group>
<mt-action-menu-item
icon="regular-cog"
@click="onMyAction"
>
My entry
</mt-action-menu-item>
</mt-action-menu-group>
{% endblock %}Restructured blocks in sw-page.html.twig
- A new
sw_page_top_barblock wraps the top bar.sw_page_top_bar_actionsis no longer nested insidesw_page_search_bar— overrides that copied the previous markup render the top bar actions twice. - The root element of
sw_page_smart_barchanged from a<template>to a<div class="sw-page__smart-bar">.
Smart bar back button styling removed
The .smart-bar__back-btn CSS ruleset was removed from sw-page.scss. #smart-bar-back slot overrides that render a bare <router-link class="smart-bar__back-btn"> with icons lose their styling. Migrate to the pattern the default back button uses:
<template #smart-bar-back>
<router-link
v-slot="{ href, navigate }"
:to="myBackRoute"
custom
>
<mt-button
is="a"
class="smart-bar__back-btn"
variant="secondary"
size="default"
square
:href="href"
:aria-label="$t('global.sw-page.backButton')"
@click="navigate"
>
<mt-icon
name="solid-long-arrow-left"
size="12px"
/>
</mt-button>
</router-link>
</template>Removed snippets and static asset
global.sidebar.buttonCollapsehas been removed.sw-extension.sw-extension-app-module-error-page.error.phraseand.error.infohave been removed; the app module error page usesmt-empty-statewith the new.error.descriptionsnippet.- The static asset
static/img/error-pages/app-error.svghas been removed. External URLs pointing to it return a 404.
Extension SDK: ui.sidebar.close() closes asynchronously
Closing a sidebar via the Extension SDK now plays a close animation (~400 ms) before the sidebar deactivates, instead of removing it immediately. With prefers-reduced-motion the sidebar still closes without delay. Do not rely on the sidebar being gone synchronously after calling close().
sw-page and sw-meteor-page are CSS containers
Both page components declare container-type: inline-size. This creates a new containing block and stacking context: plugin content inside a page that uses position: fixed is now positioned relative to the page container instead of the viewport, and z-index values no longer compete with elements outside the page.
Color props without effect
sw-search-bar:entitySearchColorand theentityIconColorprop ofsw-search-bar-itemare only applied while the user set the "Module colors" preference to "Colored" (see below). By default search results use the standard icon colors.
Optional module icon colors
The main menu and the search bar no longer color their icons by the color of the registered module. Users who prefer the previous look can switch the icons back to their module color with the "Module colors" setting in their profile settings (Profile settings > User interface). It defaults to "Neutral" and is stored per user in the core.userModuleIconColors user configuration.
The color property of Module.register() is unchanged and keeps feeding these icons, so extensions do not need to adapt.
Individual promotion codes are released again when their promotion line item is deleted
Deleting a promotion line item from an order (or deleting the order) now releases the redeemed individual promotion code, so the customer can use it again. This restores the 6.6 behaviour that was lost in the 6.7 rewrite of PromotionRedemptionUpdater: since 6.7.0.0 a used individual code stayed permanently redeemed even after a merchant removed the promotion from the order — a common workflow when a payment fails and the order is edited, which previously forced merchants to generate and send a new code. The release is scoped to codes whose redemption payload references the order the line item is deleted from; the promotion usage counters were already recalculated correctly and are unchanged.
Bulk operations in the My Extensions listing
The "My Extensions" listing can now act on several extensions at once instead of one card at a time, which noticeably speeds up maintaining shops with many extensions. Selecting one or more extensions replaces the listing controls with a bulk actions bar.
The bar offers the same actions already available per card:
- Install, activate, deactivate, update, and uninstall for all selected extensions in one step.
- Each action is enabled only when it applies to at least one selected extension (for example, activate counts only "installed but inactive" extensions) and shows how many of the selection it affects.
- All actions respect the existing
system.plugin_maintainpermission and the runtime extension-management setting, exactly like the single-card actions.
The listing reloads once after the batch finishes rather than after every individual extension. With nothing selected, the listing behaves exactly as before, so the feature is fully opt-in.
Product detail empty states use mt-empty-state
The empty states of the product detail tabs "Advanced pricing" and "Cross Selling" now render mt-empty-state instead of custom markup with an illustration. The headline, description, icon and the link to the parent product are mt-empty-state props.
The Twig blocks of sw-product-detail-context-prices.html.twig and sw-product-detail-cross-selling.html.twig that wrapped the image, icon and description texts still exist with their previous render conditions, but are empty and deprecated for removal in v6.8.0.
sw_product_detail_cross_selling_empty_state_icon and sw_product_detail_cross_selling_empty_state_actions no longer wrap a <template #icon> / <template #actions>; overrides that reproduced these slot wrappers must drop them. The add button lives in sw_product_detail_cross_selling_empty_state_actions_add inside the button slot of mt-empty-state.
The assetFilter computed of both components is deprecated for removal in v6.8.0; use Shopware.Filter.getByName('asset') instead.
The classes .sw-product-detail-context-prices__parent-prices-link and .sw-product-detail-cross-selling__parent-cross-sellings-link no longer exist; the parent link is rendered as .mt-empty-state__link.
Native-setup editor support comes from the extension tooling
Editor support for native-setup authoring is now generated by the extension tooling instead of copied by hand. Run composer admin:setup-extension-tooling (or bin/console administration:setup-extension-tooling in a Composer install): the generated ESLint config declares the compile-time macro globals (swDefinePublic, swDefineOverride, useSwPreviousState, useSwProps, useSwContext) and enables the sw-core-rules/valid-shopware-setup and sw-core-rules/native-setup-filename guards, and the generated type surface carries the macro declarations so they type-check.
The workspace templates in build/vue-setup-transform/templates/custom-plugin-workspace are removed with it. They imported eslint-plugin-vue and @typescript-eslint/parser through explicit paths into the Administration's node_modules, so a dependency bump broke every copied workspace at once. If you copied eslint.config.mjs to custom/eslint.config.mjs or plugin-tsconfig.json to custom/plugins/<PluginName>/tsconfig.json, delete them and run the setup command instead.
Extension pages use mt-empty-state
The empty states of Extensions > My extensions (previously a sw-meteor-card with custom markup) and the extension store landing page (previously custom markup with an illustration) now render mt-empty-state. The existing Twig blocks are unchanged and wrap the new markup.
The assetFilter computed of sw-extension-my-extensions-listing and sw-extension-store-landing-page is deprecated for removal in v6.9.0; use Shopware.Filter.getByName('asset') instead.
The landing page copy moved to the new snippets sw-extension-store.landing-page.activationHeadline and .activationDescription; shops that overrode the removed keys below need to move their text there. The "Now available" badge (landing-page.label) was removed without replacement:
sw-extension-store.landing-page.labelsw-extension-store.landing-page.activationDescriptionTitleFirstsw-extension-store.landing-page.activationDescriptionTitleSecondsw-extension-store.landing-page.activationDescriptionTitleDescription
The class .sw-extension-store-landing-page__wrapper-label no longer exists; .sw-extension-store-landing-page__wrapper no longer carries a background, border or fixed width, and __wrapper-content / __wrapper-activated no longer carry styles.
Native-setup components expose their swDefinePublic() bindings to parents
swDefinePublic({ ... }) now also calls defineExpose() internally with the same arguments to make its exposure symmetrical to the override surface call:
const opened = ref(false);
swDefinePublic({ opened });
// a parent: treeItem.value.opened = false;The component's props are exposed alongside them and need no declaration, so ref.value.label keeps working; they are read-only, as they are for the component itself.
Calling defineExpose() yourself is rejected in base and override components: in base mode swDefinePublic() already calls it for you, in override mode you're unnable to use it.
Extension empty states use mt-empty-state
The empty states of Extensions > My extensions and the Shopware Store activation page render mt-empty-state. The Twig blocks and snippet keys are unchanged, but overrides that build on the previous markup need to adapt: the listing empty state is no longer a sw-meteor-card, and on the activation page the "Now available" badge (.sw-extension-store-landing-page__wrapper-label) and the sw-label of the success and error states no longer exist.
The assetFilter computed of both components is deprecated for removal in v6.9.0; use Shopware.Filter.getByName('asset') instead.
Storefront
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.
Separate legal guarantee notice
The combined checkout.confirmTermsTextModalWithGuarantee snippet was replaced by checkout.confirmTermsTextModal for terms and checkout.confirmLegalGuaranteeNotice for the separate guarantee notice. Update theme overrides accordingly.
Static theme compilation without a database
Theme compilation with StaticFileConfigLoader now refreshes runtime configuration values when a database is available, while continuing to work without a reachable database in build environments.
robots.txt allows crawling thumbnails
The default storefront robots.txt now contains Allow: /thumbnail/*?ts= alongside the existing rules Disallow: /*? and Allow: /media/*?ts= to allow crawling thumbnails by bots.
Passive privacy notices without a checkbox
Storefront privacy notices now use passive wording when core.loginRegistration.requireDataProtectionCheckbox is disabled. Contact and newsletter forms use the privacy-only snippet keys contact.privacyNoticeTextModal and contact.privacyNoticeInformation, while forms that include the terms of service use general.privacyNoticeTextModal and general.privacyNoticeInformation. Themes and custom snippet sets can override the new *.privacyNoticeInformation keys to adjust the non-blocking notice.
The regular registration action now uses account.registerSubmit, while the checkout registration and guest-order authentication use checkout.registerSubmit.
Semantic footer markup
With v6.8.0.0 the footer (layout/footer/footer.html.twig) will use semantic elements.
- Collapse section headlines will become
<h2>instead of<div role="heading">. - Footer columns wrapper will become
<ul>instead of<div role="list">(role="list"is kept so Safari/VoiceOver still exposes it as a list). - Footer column will become
<li>instead of<div role="listitem">.
Clear message when adding a second code of the same promotion
Applying a second (individual) code that belongs to a promotion already present in the cart no longer fails silently or shows a generic error. The redundant code is dropped and the customer is informed with a dedicated notice, because a promotion can only be applied once per order. The message uses the new snippet key checkout.promotion-not-eligible-already-added, which theme and translation developers can override.
Edit order page selects the payment method of the order
The payment selection on /account/order/edit/{orderId} belongs to the order instead of the session. Shopware\Storefront\Page\Account\Order\AccountEditOrderPage::getSelectedPaymentMethodId() returns the payment method of the order, or the one the customer picked on the page, and the templates of that page use page.selectedPaymentMethodId instead of context.paymentMethod.id. Themes that override page_checkout_aside_actions_payment_method_id or page_checkout_change_payment_form should do the same.
frontend.account.edit-order.change-payment-method passes the selected method to the edit order page as the paymentMethodId query parameter. It still switches the payment method of the sales channel context, so templates that render context.paymentMethod on that page keep working, but that context switch is deprecated and will be removed with v6.8.0.0.
Postal codes are validated in the browser
The country <option> rendered by component/address/field/address-country-field.html.twig now carries data-zipcode-pattern and data-check-zipcode-pattern, next to the existing data-zipcode-required. CountryStateSelectPlugin reads them and applies the pattern to every [data-input-name="zipcodeInput"] field, so an invalid postal code is reported on submit instead of only after the rest of the form passes.
If your theme overrides component_address_field_country and renders its own <option> markup, add both attributes to keep postal code validation working in the browser. Server-side validation is unchanged.
Essential characteristics render select, entity and price custom fields
Custom fields of the types select, entity and price are now rendered when they are part of a product's essential characteristics. Their line item payload gained an optional display key next to the untouched content:
lineItem.payload.features[].value = { id, type, content, display }
display holds a list of resolved option or entity labels for select and entity, and the price of the current currency and tax state as a float for price. It is only present on line items built after the update, so templates overriding component/product/feature/types/feature-custom-field.html.twig must treat it as optional. A characteristic that cannot be resolved is dropped from the payload, and component/product/feature/item.html.twig no longer emits an empty list item for a characteristic its template renders nothing for.
Accessibility improvements for cart quantity changes
Changing a quantity in the cart, off-canvas cart and checkout confirm no longer submits the form on every arrow key press; the value is applied once the edit is finished or confirmed with Enter. Custom change listeners on the quantity form therefore only see the finished value. These committed events carry detail.submitImmediately: true, allowing form handlers to bypass their delay and cancel pending updates while retaining their configured submission behavior.
The buy button shows a loading indicator while the product is added
AddToCartPlugin puts a loading indicator on the buy button when the form is submitted and removes it once the off-canvas cart has opened or the request is through. The button is disabled in the meantime, so a second click can no longer add the product a second time.
The button is looked up with the plugin's existing buyButtonSelector option, which defaults to button[type="submit"].btn-buy. The new loadingIndicatorPosition option (before, after or inner, default inner) controls where the indicator is rendered. A buy button that does not match buyButtonSelector is left untouched.
Dispatching a removeLoader event on the form removes the indicator and re-enables the button, the same as with FormHandler and FormSubmitLoader. Use it when your own code needs to release the button before the request is through; removeLoadingIndicator() on the plugin instance does the same.
Themes inherit snippets from every theme in configInheritance
A theme that lists several ancestors in the configInheritance of its theme.json now receives the snippets of all of them. Storefront texts can change where an intermediate theme defines a snippet key that was dropped until now.
theme.parent_theme_id now points to the nearest listed ancestor. Run bin/console theme:refresh to apply it outside a plugin or update cycle.
Hosting & Configuration
No-Vary-Search header on cacheable responses
With CACHE_REWORK active, cacheable storefront and store-api responses send No-Vary-Search: key-order, declaring that the order of query parameters does not change the response. The server already normalizes the query order before it looks up its cache entry, so the header only tells clients what was always true.
The header is a specification draft, support differs per browser and per cache, and it does not replace query sorting in a reverse proxy such as Varnish or Fastly. A client that ignores it keeps treating a reordered query string as a different URL, which is the behaviour you have today.
Set your own value per policy under headers.no_vary_search, for example no_vary_search: 'key-order, params=("gclid")'. It is passed through verbatim, validated only for being a single line of printable ASCII. If the key is omitted from the policy, no No-Vary-Search header is sent, and any value a controller or plugin set earlier in the request is removed. Unlike Cache-Control, the header cannot be influenced by a #[HttpCache] attribute. The policy is its only source.
Never list parameters that change the rendered content, such as p, order, search or filter names. A client would then match a stored response against the wrong URL and show page 1 at a ?p=2 URL. Tracking parameters are safe, because reuse does not rewrite the document URL.
Registering public OAuth clients
Public OAuth clients that may use the authorization code grant are configured under shopware.api.oauth_clients. Shopware ships shopware-cli with the loopback redirect URIs http://127.0.0.1/callback and http://[::1]/callback. Loopback URIs accept any port (RFC 8252), all other redirect URIs must match exactly. Additional clients are added per project:
shopware:
api:
oauth_clients:
my-tool:
name: 'My Tool'
redirect_uris: ['http://127.0.0.1/callback']The lifetime of authorization codes is configurable with shopware.api.auth_code_ttl (default PT5M).
Legal guarantee notice on the registration and other privacy notices
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.
App System
Target validation can be disabled for local development
The new shopware.app_system.enable_url_validation option turns off app system and webhook target validation, including the HTTPS requirement, the private network checks and the DNS pinning. It defaults to true and is shipped as false for the dev environment, so local app and webhook endpoints work over HTTP and on private or unresolvable hosts without further configuration.
While it is false, shopware.app_system.allow_unencrypted_traffic and shopware.app_system.allowed_private_ip_addresses have no effect. Keep the validation enabled in production.
What's Changed
- feat: png GARAN label for order confirmation email (backport: 6.7.15.x) by @socrec #20763
- feat(core): let callers supply the combinations a configurator is built from by @vienthuong #20641
- feat(core): connect-time toolset selection via ?toolsets by @dnoegel #20509
- feat(framework): upgrade symfony/mcp-bundle to 0.13 and mcp/sdk to 0.8 by @BrocksiNet #19963
- feat(core): declare No-Vary-Search on cacheable responses by @BrocksiNet #18835
- feat(administration): track applied appearance settings in product analytics by @arnoldstoba #20442
- feat(core): reconcile installed services in one pass by @AydinHassan #20127
- feat: Add new helper method to IdSearchResult for common update and delete cases by @mitelg #20413
- feat: rework the Shopware update module by @shyim #19666
- feat(framework): make the store-api CORS header allow-list extensible by @mstegmeyer #20323
- feat: add OAuth authorization code grant with PKCE for public clients by @shyim #20277
- feat(core): Extend country advanced postal code pattern length to 1024 by @aragon999 #20275
- feat: Allow crawling of thumbnails in
robots.txtby @aragon999 #20244 - feat: reject colliding document filename infixes by @vintagesucks #20078
- feat(administration): add hotkeys to toggle the navigation and switch the appearance by @arnoldstoba #20084
- feat(storefront): align theme list actions with cms list page by @fabianhueske #20185
- feat(languages): Add brazil to language procedures by @marcelbrode #20184
- feat: Add type safe uuids in administration by @gecolay #18278
- feat: colour media folders with the module colour setting by @fabianhueske #20085
- feat(framework): disable app system target validation in dev by @mstegmeyer #19835
- feat: dispatch document v1 events for compatibility by @jozsefdamokos #19761
- feat(modal): add 'x-large' variant and custom z-index support by @magdakok #19321
- feat(administration): allow suppressing order confirmation mail by @KnollElias #18408
- feat(framework): guard covers targets against the coverage source by @nfortier-shopware #19644
- feat: sitemap generation for headless sales channels by @mateuszfl #19688
- feat: Add more specific messages to the promotion cart errors by @aragon999 #17278
- feat(framework): detect value mutation and self-calls in the coverage-ignore rule by @nfortier-shopware #19879
- feat(discovery): install translations offline by @MartinKrzykawski #19813
- feat: keep and link the accessible html document in v2 by @vintagesucks #19722
- feat(administration): make native-setup config first-class in extension tooling by @gweiermann #19278
- feat: let apps register document types and extend generation by @larskemper #19199
- feat(elasticsearch): index storefront ES after system:install by @shyim #19845
- feat(framework): allow extending JsonField mappings via EntityExtension by @shyim #19665
- feat(framework): scope the createMock expectations rule via phpstan configuration by @nfortier-shopware #19798
- feat: document v2 storefront support by @jozsefdamokos #19472
- feat: add bulk operations to My Extensions listing in administration by @aiomayo #17145
- feat: after-sales document events by @jozsefdamokos #19026
- feat: admin ui shell rework by @alastair-simon #15271
- feat: document v2 bulk edit by @jozsefdamokos #18925
- feat: improve footer semantics with native elements by @tobiasberge #19409
- feat(checkout): record the origin of an order state transition by @mstegmeyer #19614
- fix(storefront): link legal guarantee notice from privacy notices (backport: 6.7.15.x) by @socrec #20873
- fix(storefront): keep the checkout consent across reloads without outliving the session (backport: 6.7.15.x) by @mstegmeyer #20735
- fix(storefront): use product ID for order Garan labels (backport: 6.7.15.x) by @daniel3010 #20704
- fix(storefront): separate legal guarantee notice (backport: 6.7.15.x) by @namdinh-92 #20700
- fix(core): align checkout gateway Store API schema with the real response by @mkucmus #20606
- fix(after-sales): simulate mapping entities as ArrayEntity instead of a bare Entity by @nfortier-shopware #20626
- fix(framework): preserve empty system config overrides by @keulinho #20283
- fix: Ensure compatibility with Twig 3.29 by @mitelg #20621
- fix(checkout): cancel transaction on amount change by @namdinh-92 #20548
- fix: normalize VAT IDs before validation by @namdinh-92 #20494
- fix(administration): keep an empty string on fields that allow one by @silverDuy #20173
- fix(core): correct customer-recovery-is-expired store-api schema by @app/github-actions #20556
- fix(snippets): Adjust placeholder wording by @marcelbrode #20608
- fix: dispatch MediaFileExtensionWhitelistEvent only once per /api/_info/config request by @lunetics #18899
- fix(core): drop stale local copies of app storefront snippets by @rittou #20566
- fix: mail simulate a11y documents by @jozsefdamokos #20501
- fix(core): reset derived fields when cloning by @keulinho #20575
- fix: add sw-include-seo-urls to the HTTP cache variant headers by @mkucmus #20439
- fix: inject translator when sending mail by @jozsefdamokos #20444
- fix: keep document as a sub entity of order by @vintagesucks #20526
- fix(administration): align header for currency dependent pricing modal by @nguyenquocdaile #20491
- fix(dal): preserve scores for paginated associations by @keulinho #20530
- fix(storefront): handle unavailable database in static theme compilation by @joberthel #20495
- fix(storefront): center standard gallery images by @joberthel #20458
- fix: invisible recaptcha double submit by @dneustadt #20453
- fix(administration): stop offering to cancel an already-cancelled subscription by @mstegmeyer #20547
- fix: add file-accept to media modal in product base. by @feliopterix #20507
- fix(administration): align card title and toolbar styling by @fabianhueske #20335
- fix(administration): color the active flyout item with the module color by @alastair-simon #20247
- fix(administration): increase the collapsed admin menu flyout offset by @fabianhueske #20415
- fix(ci): polyfill URL.canParse for release info generator by @fruppel #20503
- fix(core): use CustomerAddressBody schema for register billing/shipping address by @namdinh-92 #20404
- fix(administration): pass the real error to onError in sw-order-detail by @namdinh-92 #20393
- fix: recover stale sales channel context options by @keulinho #19924
- fix(core): keep the criteria sorting when grouping is score ranked by @tamvt #20333
- fix: metrics configuration missing labels by @h1k3r #20430
- fix(administration): refetch flow with page criteria in updateSequences by @app/github-actions #20421
- fix: ensure valid number format in sitemap by @cngJo #20341
- fix(administration): category/tags description overlap in de-DE by @wakqasahmed #20107
- fix(core): prevent duplicate-key error in SEO URL canonical promotion for products in multiple same-language sales channels by @golliholzland #20397
- fix(core): keep dynamic group mapping writes free of FK violations by @tamvt #20303
- fix: serve app storefront snippets from persisted snapshots by @Gaitholabi #20279
- fix(administration): enforce the GARAN duration range and surface unmet label prerequisites by @mstegmeyer #20141
- fix(core): promote customer_id from sales_channel_api_context column on load by @augsteyer #19261
- fix(storefront): load snippets from every configInheritance parent by @dgrothaus-sw #20208
- fix(core): size the GARAN label in the order confirmation mail by @mstegmeyer #20140
- fix(inventory): let variants inherit the GARAN label confirmation by @mstegmeyer #20139
- fix(checkout): validate storefrontUrl only when double opt-in is enabled by @untilu29 #20320
- fix: StoreApi cache serves wrong language with CACHE_REWORK enabled by @h1k3r #20200
- fix(inventory): keep GARAN label values inside the artwork by @mstegmeyer #20138
- fix(core): score grouped entities by their best matching entity by @shyim #19139
- fix(administration): rename jest.config.ts so jest picks a single config by @gweiermann #20201
- fix: Switch replica connection back to the replica between requests by @mateuszfl #18857
- fix(administration): stabilize remaining major Jest tests by @keulinho #20213
- fix(administration): let only a super admin toggle the user admin flag by @umutdogan4291 #20285
- fix(storefront): commit cart quantity changes once the edit is finished by @mstegmeyer #20055
- fix(storefront): read the score sorting label from the database again by @vienthuong #20265
- fix(administration): stretch advanced pricing price fields by @sydinh #20262
- fix: gracefully skip concurrent sitemap generation again by @mateuszfl #19687
- fix(administration): generate defineExpose from swDefinePublic by @gweiermann #20004
- fix(ci): remove duplicate permissions in npm-audit-check workflow by @shyim #20252
- fix(administration): pin patched @tiptap/core for GHSA-cp6q-959q-f8rh by @patzick #20239
- fix: add missing flow trigger snippets by @vintagesucks #20202
- fix: reset flow rule scope cache between requests by @vintagesucks #20175
- fix: reindex product stream mapping on filter delete and update by @vienthuong #19583
- fix(administration): align manufacturer and promotion empty-state wording by @fabianhueske #20077
- fix(administration): keep sw tabs legacy in 6.8 by @keulinho #19757
- fix(checkout): report promotion codes that grant no discount by @mstegmeyer #20050
- fix(administration): use surface and border tokens in CMS editor by @fabianhueske #20183
- fix: exclude feed sales channels from currency validation by @dneustadt #20128
- fix(snippets): Fix several language issues by @marcelbrode #20177
- fix(administration): prefer the resolved media item url in the image slider settings preview by @nfortier-shopware #20038
- fix(administration): allow selecting a customer's language on creation by @mstegmeyer #20108
- fix(administration): disabled entity-select clearing an unresolved bound value by @wakqasahmed #20080
- fix(checkout): version order and category tag assignments by @mstegmeyer #20054
- fix(checkout): add snippet for the unknown promotion condition warning by @mstegmeyer #20053
- fix(administration): clean up order versions on pagehide by @daniel3010 #20003
- fix(administration): discard invalid promotion code tags by @daniel3010 #19896
- fix(administration): rework the cms layout listing header and grid by @fabianhueske #19710
- fix: include seo urls in store-api responses for headless sales channels by @mateuszfl #19686
- fix(checkout): reject ordering a cart that was already consumed by @mstegmeyer #20097
- fix(administration): default the interface theme to light by @fabianhueske #19834
- fix: cms product listing filter inheritance by @dneustadt #20075
- fix(storefront): handle relative manufacturer links by @LukasVoeller #19984
- fix(checkout): apply line item factory decorators when adding a product to an order by @vienthuong #19609
- fix(framework): guard JsonEntityEncoder against missing struct vars by @umutdogan4291 #19826
- fix(framework): return 401 for OAuth errors on the admin MCP endpoint by @umutdogan4291 #19831
- fix(framework): handle missing required key in debug:mcp tool detail by @umutdogan4291 #19833
- fix(ci): unblock sw-bugfixer from shallow checkout and exact-match bash rules by @mstegmeyer #19932
- fix: order dependent indexers by @keulinho #19987
- fix(administration): clean up stale address required-field warnings by @app/github-actions #19885
- fix(framework): follow parent method chains in the createMock expectations rule by @nfortier-shopware #19916
- fix: avoid undefined array key warnings in the twig component pass and the cheapest price test by @nfortier-shopware #19968
- fix(administration): keep imitate customer redirect inside the user gesture by @mstegmeyer #19877
- fix: correct payment reminder mail subject by @DennisGarding #19920
- fix(after-sales): boot the rate-limited document kernel inside the first test by @nfortier-shopware #19978
- fix(framework): resolve translated fields per definition instance in the entity hydrator by @nfortier-shopware #19931
- fix(categories): Show product breadcrumb for categories hidden in nav by @marcelbrode #19989
- fix(product-slider): Variant-infos for product boxes by @marcelbrode #19992
- fix: review filter checkmark by @jozsefdamokos #19990
- fix(discovery): keep pseudo-locales out of translation:install --all by @MartinKrzykawski #19993
- fix(administration): limit mt-tabs attribute forwarding by @keulinho #19607
- fix: validate supplied sales channel currency by @keulinho #19836
- fix(checkout): enforce one destination per state machine action and source state by @mstegmeyer #19793
- fix: shopware account typo by @bojanrajh #19942
- fix(sales-channel): add ACL guards to agentic files feature by @keulinho #19901
- fix(administration): restore bulk edit custom field toggles by @keulinho #19668
- fix(storefront): block the buy button while the product is added by @mstegmeyer #19910
- fix(administration): date a transaction's initial state by its creation time by @mstegmeyer #19908
- fix: allow documents without order reference by @DennisGarding #19678
- fix(administration): ignore stale browserslist data warning in Jest by @shyim #19959
- fix: treat empty document prefix and suffix as unset by @vintagesucks #19623
- fix(administration): lint extension .vue files with the same type-aware rules as .ts by @gweiermann #19320
- fix(administration): remove the dead clear button from order state selects by @mstegmeyer #19882
- fix(checkout): prevent duplicate customer registration on double submit by @daniel3010 #18361
- fix: add custom ref to prevent computed evaluation during setup by @davidtraum #19756
- fix(storefront): select the payment method of the order on the edit order page by @mstegmeyer #19883
- fix(core): repair the invalid American Samoa postal code pattern by @mstegmeyer #19891
- fix: empty array in infix field of document configuration database entry by @CR0YD #19859
- fix: document v2 creation error messages in administration by @CR0YD #19812
- fix: change gdpr information snippet by @En0Ma1259 #15471
- fix: remove route attribute duplicates by @lacknere #19758
- fix(promotion): normalize promotion codes by @keulinho #19900
- fix(checkout): release individual promotion codes when their line item is deleted by @MamaTux #19211
- fix(checkout): show translated cart errors in the Administration by @app/github-actions #19886
- fix(storefront): validate the zip code pattern in the browser by @mstegmeyer #19892
- fix(framework): stop env vars changed in a test from leaking into the container by @mstegmeyer #19893
- fix(administration): stabilize sales channel default select rendering by @joberthel #19842
- fix(administration): allow dropping categories into empty folders by @joberthel #19760
- fix(inventory): show custom field essential characteristics in the cart by @mstegmeyer #19880
- fix(core): apply product sortings registered at runtime by @nguyenytran #16213
- fix(administration): keep first run wizard step after plugin activation reload by @app/github-actions #19884
- fix(administration): open the inline edit of a newly added order line item by @mstegmeyer #19878
- fix(checkout): clarify order cancellation setting label by @mstegmeyer #19873
- fix: reject retained calls to removed hierarchy parents by @keulinho #19866
- fix(store-api): preserve fields from compressed criteria by @keulinho #19863
- fix(core): keep download access rule migration blue-green safe by @DennisGarding #19856
- fix(storefront): firefox order status badge by @tobiasberge #19693
- fix(storefront): correct misleading order cancellation dialog wording by @daniel3010 #19825
- fix(administration): clear stale license violations by @keulinho #19673
- fix(administration): use verification modal for user deletion by @keulinho #19407
- fix: validate ClassHierarchyChange method compatibility by @keulinho #19819
- fix(framework): keep dal validate running for invalid definitions by @keulinho #19821
- fix: proper validation failure for guarantee months by @lernhart #19818
- fix: react to native SFC edits in the vite dev server by @davidtraum #19672
- fix(administration): keep components built out of sw-block single-rooted by @jleifeld #19704
- fix(administration): unblock the SFC migration tooling by @jleifeld #19622
- fix(administration): preserve SSO select options in major tests by @keulinho #19605
- fix(administration): align schema expectation with major flag by @keulinho #19604
- fix(administration): align shared admin component styles by @fabianhueske #19714
- fix(storefront): correct the theme manager card padding and borders by @fabianhueske #19713
- fix(administration): use elevation surface tokens for module toolbars by @fabianhueske #19715
- fix(administration): admin menu active state plugin detail routes by @alastair-simon #19723
- fix(administration): prevent false unsaved changes warning by @nguyenquocdaile #19682
- fix(framework): keep merchant edited app payment method texts by @daniel3010 #19698
- fix(administration): clone translated CMS element config on init by @mynetx #19735
- fix: extension added document v2 types label by @jozsefdamokos #19675
- fix: grammar in unsaved order warning message by @cramytech #19738
- fix(store-api): restrict context token header by @patzick #19547
- fix(storefront): add title and remove duplicate main landmark from default 404 page by @joberthel #19540
- fix: initialize navbar aria-current state by @joberthel #19707
- fix(storefront): add accessible name to line item collapse button by @joberthel #19680
- fix(administration): restore currency price input width by @nguyenquocdaile #19731
- fix(snippets): Correct snippets to be en-GB instead of en-US by @marcelbrode #19762
- fix(administration): calculate linked net price in shipping price matrix by @daniel3010 #19677
- fix(checkout): dedicated error code for invalid child line item quantity by @app/github-actions #19794
- fix: emit zugferd allowance / charge groups on tax free orders by @larskemper #19657
- fix(storefront): do not persist terms of service acceptance across checkout sessions by @app/github-actions #19799
- fix(checkout): show clear message when adding a second code of the same promotion by @app/github-actions #19801
- fix(storefront): convert delivery times to whole days in JSON-LD by @joberthel #19670
- fix(core): reference price in documents by @mkreusch #19355
- fix: set correct customer detail id upon navigations by @socrec #19415
- fix(administration): keep variant characteristics of deleted products in orders by @vienthuong #19438
- fix: merge document filename infixes per format by @vintagesucks #19617
- fix: synchronize customer number ranges after import by @DennisGarding #19277
- fix(storefront): preserve video playback state across slider rebuilds by @damian-pastorini #19600
- refactor(administration): don't wrap arrays that have only a few entries by @gweiermann #20593
- refactor(administration): introduce module colour variables and align module icons by @fabianhueske #20103
- refactor: Simplify return statements by @aragon999 #18962
- refactor(administration): use mt-empty-state on the extension pages by @fabianhueske #19712
- refactor(administration): use mt-empty-state on the product detail tabs by @fabianhueske #19711
- refactor(core): migrate BC planning and property attributes by @keulinho #19660
Full Changelog: v6.7.14.2...v6.7.15.0
Get in touch
Discuss about decisions, bugs you might stumble upon, etc in our community discord. See you there ;)