Skip to content

Release notes Shopware 6.7.13.0

5.8.2026

Release notes Shopware 6.7.13.0

Abstract

This release resolves 231 issues, focusing on improving reliability and developer experience. It addresses critical bugs in product streams, email template cloning, and currency formatting, while also refining caching behavior, category indexing performance, and Elasticsearch product mapping. Additionally, it introduces new Admin API endpoints for translation management and enhances the MCP server with toolsets and list-change notifications.

System requirements

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

Improvements

Critical Fixes

Store API requests no longer start PHP sessions

Store API requests now remain stateless unless application or extension code explicitly starts a session. Previously, several sales channel and Storefront event subscribers could initialize Symfony's lazy session factory during Store API requests, causing unnecessary session storage growth and potentially taking PHP session locks. Storefront session handling, including customer imitation, remains unchanged.

Core

Product descriptionTeaser backfill runs once as a post-update indexer

The product.description_teaser.indexer that fills descriptionTeaser for products predating the column (introduced in 6.7.12) is now a one-time post-update indexer: it runs once through the post-update flow after the update and is no longer executed by bin/console dal:refresh:index. It rebuilds each teaser from the current description and rewrites only the rows whose stored value is missing or out of date. Ongoing changes continue to be kept in sync synchronously on write by the product description-teaser subscriber.

Agentic file names are matched case-insensitively

Agentic file names are now matched case-insensitively. Core uses the standard /AGENTS.md spelling, while existing /agents.md URLs, lowercase extension templates, enabled states, and merchant overrides continue to work.

Shopware Services are updated by the scheduled service task

The daily services.install scheduled task now runs a reconcile pass: in addition to installing newly-registered Shopware Services, it converges every already-installed service to the latest revision advertised by the service registry. Previously an installed service was only updated when it pushed an update via POST /api/services/trigger-update, so a shop that missed a push stayed on a stale revision until the next push. Updates are idempotent — a service already on the latest revision is a no-op — and no configuration change is required (services must be enabled as before).

Per-thumbnail post-processing event and progressive JPEG thumbnails

Thumbnail generation gained a new extension point and two output improvements:

  • A new event Shopware\Core\Content\Media\Event\ThumbnailGeneratedEvent is dispatched after each individual thumbnail is written to disk. Until now there was no hook for post-processing a single thumbnail — MediaPathChangedEvent only fires once for the whole batch — so plugins that want to optimise thumbnails (e.g. jpegoptim, pngquant) had nowhere to attach. The event exposes the mediaId, thumbnailId, thumbnail path, mimeType and the FilesystemOperator the thumbnail was written to, so a subscriber can read the file back, optimise it and write it again.
  • JPEG thumbnails are now written as progressive (interlaced) JPEGs. Progressive JPEGs show a full low-quality preview immediately and sharpen as they load, improving perceived load time (LCP) on slow connections; file size stays equal or slightly smaller. This is transparent — no action required.
  • Batch thumbnail generation is now resilient: a single unprocessable media (corrupt source, unsupported format, filesystem error or a throwing ThumbnailGeneratedEvent subscriber) is logged and skipped, and its partially written files are cleaned up, so the remaining media in the batch still get their thumbnails instead of the whole run aborting. The single-media path (Shopware\Core\Content\Media\Thumbnail\ThumbnailService::updateThumbnails()) still surfaces the exception to its caller. (shopware/shopware#18250)

Installed translations are refreshed automatically by a scheduled task

A new translation.update scheduled task now keeps installed translations up to date without manual intervention. It runs once a day by default and does the same work as the translation:update console command / POST /api/_action/translation/update route: it fetches the latest remote metadata and re-downloads every installed locale whose translation changed. Shops without any installed translation are a no-op and make no remote request.

Operators can change the interval like any other scheduled task (scheduled_task.run_interval) or disable it entirely with bin/console scheduled-task:deactivate translation.update.

The translation update orchestration was extracted into the new internal service Shopware\Core\System\Snippet\Service\TranslationUpdater, which the Admin API route and the scheduled task share. The HTTP contract of POST /api/_action/translation/update is unchanged, except that it now short-circuits without a remote request when no translation is installed (the response is identical).

Cloning an entity no longer fails on the write-protected wasModifiedByUser field

Cloning any entity that carries a wasModifiedByUser field previously always failed with FRAMEWORK__WRITE_CONSTRAINT_VIOLATION on wasModifiedByUser, because the clone copied that write-protected field's value into the insert payload. In the Core this affected mail templates (e.g. via POST /api/_action/clone/mail-template/{id}), and it applies equally to any extension entity using the field. The clone process now omits the field, so the cloned entity is correctly created as a fresh, non-user-modified record. (shopware/shopware#18233)

Standard integrations now honor sw-app-user-id

Admin API requests authenticated with a standard integration access key now support the sw-app-user-id header the same way app integrations already do. When the header contains a valid user id, the resolved permissions are restricted to the intersection of the integration ACL privileges and the user's ACL privileges. Invalid or empty sw-app-user-id values continue to be ignored.

Cache invalidated on cross-selling updates and deletions

Editing or deleting a product cross-selling entry, including assigned products and translations, now correctly invalidates the product detail route cache and prevents stale storefront results.

Enforce "Allow payment change after checkout" when re-paying an order

Shopware\Core\Checkout\Order\SalesChannel\SetPaymentOrderRoute now rejects payment methods whose afterOrderEnabled ("Allow payment change after checkout") flag is disabled, matching the methods offered on the edit-order page. Previously the flag was only applied as a UI filter, so a payment method that renders its own JavaScript payment button (e.g. PayPal smart buttons) could still be used to pay an existing order. The store-api route POST /store-api/order/payment now returns CHECKOUT__ORDER_PAYMENT_METHOD_NOT_CHANGEABLE (HTTP 403) for such methods. (shopware/shopware#17495)

ZUGFeRD correction documents derive shipping handling from document metadata

For cancellation and other correction-style ZUGFeRD documents, delivery amounts are now serialized according to their business meaning:

  • refunded shipping is emitted as an allowance
  • charged return shipping is emitted as a charge
  • zero-value shipping is omitted from the XML entirely

Plugins that build Shopware\Core\Checkout\Document\Zugferd\ZugferdDocument instances manually should set document metadata via withDocumentInformation() before adding deliveries when they expect correction-specific shipping output. The delivery serialization now derives from the document type that was set there.

Text-based media is stored and served with an explicit charset

Text-based media files (text/plain, text/csv, text/html, text/xml, application/json, application/xml) are now written to storage with an explicit Content-Type: …; charset=utf-8. Previously the charset was missing, so serving such a file directly from object storage / CDN made browsers fall back to a non-UTF-8 encoding and render umlauts and other multi-byte characters as mojibake. This applies to both the server-side upload path and the presigned direct-to-S3 upload path. The mimeType persisted on the media entity stays bare (without the charset parameter), so no code reading it needs to change.

Webhooks are signed with the current app secret after a secret rotation

Webhook deliveries now resolve the app's HMAC signing secret at delivery time instead of reusing the secret captured when the webhook was queued. A webhook that was queued or retried across an app-secret rotation was previously still signed with the stale secret, so the receiving app rejected it with a signature error until the message was dropped. Apps no longer need to do anything — deliveries that span a rotation are signed with the secret the app currently verifies against.

SVG validator accepts more passive extension assets

SVG media validation now accepts additional passive SVG elements, attributes, metadata, inline fonts, safe animation attributes, known editor namespaces, public SVG doctypes without internal subsets, and embedded raster image data URIs. This allows more SVG assets shipped by extensions and themes to pass validation while still rejecting active content such as external references, processing instructions outside scoped metadata, foreignObject, and entity definitions.

DAL validation now checks for non-standard foreign keys (MySQL 8.4)

dal:validate detects foreign keys that reference something other than a complete PRIMARY or UNIQUE key of the target table. MySQL 8.4 rejects such FKs when restrict_fk_on_non_standard_key=ON, which breaks schema imports.

Plugin authors: if dal:validate newly fails for your plugin, the fix is to extend the FK to cover all columns of the referenced key (typically adding the missing version_id column). If you need to temporarily suppress a specific constraint name while migrating, pass --tolerate-foreign-key=<constraint_name> to the command.

Plugin activation rolls back when post-activation fails

Plugin activation now restores the plugin's active flag when a post-activation subscriber fails. Previously, a failure after the active flag was persisted, for example during storefront theme refresh, could leave the plugin marked active even though activation failed.

Deprecated maintenanceIpWhitelist wording of the sales channel

The non-inclusive maintenanceIpWhitelist wording on the sales channel is deprecated in favor of maintenanceIpAllowlist. The deprecated members keep working and will be replaced in Shopware 6.8. Migrate your code now:

  • DAL: use the new field maintenanceIpAllowlist instead of maintenanceIpWhitelist. Both fields are available and kept in sync during the transition.
  • Shopware\Core\System\SalesChannel\SalesChannelEntity: use getMaintenanceIpAllowlist() / setMaintenanceIpAllowlist() instead of getMaintenanceIpWhitelist() / setMaintenanceIpWhitelist().
  • Shopware\Core\SalesChannelRequest: use the constant ATTRIBUTE_SALES_CHANNEL_MAINTENANCE_IP_ALLOWLIST instead of ATTRIBUTE_SALES_CHANNEL_MAINTENANCE_IP_WHITLELIST.
  • Shopware\Core\Framework\Adapter\Kernel\HttpCacheKernel: use the constant MAINTENANCE_ALLOWLIST_HEADER instead of MAINTENANCE_WHITELIST_HEADER.

The new sales_channel.maintenance_ip_allowlist database column is added and kept in sync with the deprecated maintenance_ip_whitelist column. The deprecated field and column will be removed with Shopware 6.8.

Deprecated BeforeCacheControlEvent and the administration cache-control marker

Shopware\Core\Framework\Adapter\Cache\Http\Event\BeforeCacheControlEvent, Shopware\Administration\Controller\AdministrationController::CACHE_ID_HEADER and Shopware\Administration\Controller\AdministrationController::CACHE_ID_ADMINISTRATION are deprecated and will be removed in Shopware 6.8.0.0, together with the internal dispatching and consuming code.

The event and related headers only existed to skip the CacheControlListener. With the new caching (the CACHE_REWORK feature flag, which becomes the default in 6.8.0) the response Cache-Control headers will be returned to the calling client and whole construction will be removed.

Deprecated core script response rendering

Shopware\Core\Framework\Script\Api\ScriptResponseFactoryFacade::render() is deprecated and will be removed in Shopware 6.8.0.0. The method remains available in 6.7 for backwards compatibility when the Storefront bundle and a SalesChannelContext are available.

Extension authors should type the script response service for the hook they implement: use Shopware\Core\Framework\Script\Api\ScriptResponseFactoryFacade for admin-api and store-api hooks and avoid render() there; use Shopware\Storefront\Framework\Script\Api\StorefrontScriptResponseFactoryFacade for Storefront hooks that render Twig templates.

twig
{# admin-api and store-api hooks #}
{# @var services.response \Shopware\Core\Framework\Script\Api\ScriptResponseFactoryFacade #}

{# Storefront hooks #}
{# @var services.response \Shopware\Storefront\Framework\Script\Api\StorefrontScriptResponseFactoryFacade #}

Deprecated unused Composer dependencies

The following Composer dependencies are deprecated as Shopware dependencies and will be removed with the next major version:

  • doctrine/inflector
  • symfony/monolog-bridge
  • symfony/proxy-manager-bridge

If your extension uses classes from one of these packages, declare the package explicitly in your extension's composer.json. Generally, do not rely on Shopware dependencies being installed transitively.

Declarative custom fields via Resources/config/custom-fields.xml

Plugins and apps can now define custom fields declaratively in a Resources/config/custom-fields.xml file. Shopware automatically handles creation, updates, and removal during the extension lifecycle (install, update, uninstall).

This eliminates the boilerplate CustomFieldsInstaller service and plugin lifecycle hooks that were previously required for plugins. For apps, the same file-based approach replaces the inline <custom-fields> section in manifest.xml (now deprecated).

The XML format is the same one already used by apps in the manifest:

xml
<?xml version="1.0" encoding="utf-8"?>
<custom-fields xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/shopware/trunk/src/Core/System/CustomField/Schema/custom-fields-1.0.xsd">
    <custom-field-set>
        <name>my_plugin_fields</name>
        <label>My Fields</label>
        <label lang="de-DE">Meine Felder</label>
        <related-entities>
            <product/>
        </related-entities>
        <fields>
            <int name="my_plugin_weight">
                <label>Weight</label>
                <position>1</position>
            </int>
        </fields>
    </custom-field-set>
</custom-fields>

New classes:

  • Shopware\Core\System\CustomField\CustomFieldSetPersister — shared persistence logic for custom field sets
  • Shopware\Core\System\CustomField\CustomFieldXmlLoader — loads and validates custom-fields.xml files

The custom field XML DTO classes have been moved from Shopware\Core\Framework\App\Manifest\Xml\CustomField to Shopware\Core\System\CustomField\Xml to make them properly reusable outside the app system.

Custom fields can be marked searchable declaratively

Apps and plugins can now flag a custom field as searchable directly in XML via a new <include-in-search> element, setting the field's includeInSearch property on creation. For apps, declare it inside the <custom-fields> section of manifest.xml:

xml
<custom-fields>
    <custom-field-set>
        <name>swag_example_set</name>
        <label>Example</label>
        <related-entities>
            <product/>
        </related-entities>
        <fields>
            <text name="swag_example_field">
                <label>Example field</label>
                <include-in-search>true</include-in-search>
            </text>
        </fields>
    </custom-field-set>
</custom-fields>

The same element also works in the file-based Resources/config/custom-fields.xml format used by plugins and apps.

Previously includeInSearch defaulted to false and could only be toggled through the Admin UI or the Admin API — and for app-owned custom fields that was not possible at all, because their field sets are not editable in the Administration. A searchable custom field is picked up by the product search indexing and becomes selectable for ranking configuration under Settings → Search. The element defaults to false, so existing extensions are unaffected.

Scheduled task execution moved to ScheduledTaskExecutor

The orchestration logic of ScheduledTaskHandler::__invoke() (loading the task, marking it running or failed, and rescheduling it) has moved into the new ScheduledTaskExecutor service. The executor is injected into every scheduled task handler tagged as messenger.message_handler via the new ScheduledTaskExecutorCompilerPass. Scheduled task handlers registered through the container — the standard way plugins register them — require no changes and keep working as before.

The inline execution logic in ScheduledTaskHandler::__invoke() is deprecated and will be removed in Shopware 6.8.0.0. This only affects code that instantiates a ScheduledTaskHandler manually instead of resolving it from the container (for example in tests). In that case, set the executor explicitly to opt into the new behaviour, otherwise the handler falls back to the deprecated inline logic and triggers a deprecation warning:

php
$handler = new MyScheduledTaskHandler($scheduledTaskRepository, $logger);
$handler->setScheduledTaskExecutor(new ScheduledTaskExecutor($scheduledTaskRepository, $logger, $clock));
$handler($task);

The protected markTaskRunning(), markTaskFailed(), and rescheduleTask() hooks are deprecated and will be removed in Shopware 6.8.0.0. They remain overridable until then — the executor still routes through an overridden rescheduleTask() for backwards compatibility — but new code should not rely on them, as the executor owns the status transitions and rescheduling.

If you need to control when a task runs next (instead of the default now + runInterval schedule), implement the new DynamicallyScheduledTaskHandler interface rather than overriding rescheduleTask(). The executor asks the handler for the next execution time via getNextExecutionTime() and persists it, so the handler only answers the "when", not the "how":

php
use Shopware\Core\Framework\MessageQueue\ScheduledTask\DynamicallyScheduledTaskHandler;
use Shopware\Core\Framework\MessageQueue\ScheduledTask\ScheduledTask;
use Shopware\Core\Framework\MessageQueue\ScheduledTask\ScheduledTaskEntity;

class MyScheduledTaskHandler extends ScheduledTaskHandler implements DynamicallyScheduledTaskHandler
{
    public function getNextExecutionTime(ScheduledTask $task, ScheduledTaskEntity $taskEntity): ?\DateTimeInterface
    {
        // return the next execution time, or null to fall back to the default `now + runInterval` schedule
        return $this->nextPendingRecordTimestamp();
    }
}

Sales Channel business timezone

Sales Channels now have an optional business timezone setting. When configured, document rendering for that Sales Channel uses this timezone instead of Twig's default timezone.

Without a value, document rendering keeps its previous behaviour, which depends on the entry point: documents generated during a Storefront request can pick up the customer's browser timezone, while documents generated from the Administration or the message queue use Twig's configured default timezone. Starting with Shopware 6.8, this entry-point dependency is removed: without a business timezone, documents always render in Twig's configured default timezone (UTC unless changed via the twig.date.timezone configuration), regardless of how the document is generated.

EntitySearchResult and result subclasses deprecated

EntitySearchResult, ProductListingResult, and ProductReviewResult are deprecated for v6.8.0. In v6.8.0 EntitySearchResult will no longer extend EntityCollection, and the two subclasses will no longer extend EntitySearchResult. The classes remain Struct, so extensions, states, and JSON serialization keep working.

To prepare, for all three classes:

  • Call collection methods (first, last, filter, getElements, slice, …) on $result->getEntities() instead of directly on the result.
  • In Twig, use {% for x in searchResult.entities %} instead of {% for x in searchResult %}, and searchResult.entities instead of searchResult.elements.
  • Stop relying on instanceof EntityCollection for any result, or on instanceof EntitySearchResult for a ProductListingResult / ProductReviewResult. Parameter and return types declared as those will reject results in v6.8.0.

For EntitySearchResult:

  • The wrapper becomes immutable: $total, $entities, $page, $limit, $criteria, $context, and $aggregations become readonly, and the setters (setPage(), setLimit(), setEntity(), setCustomFields()) will be removed.
  • Stop using getEntity() / setEntity() and the $entity field. The entity name is no longer exposed by the result wrapper in v6.8.0.
  • Code that constructs a result directly (new EntitySearchResult(...)) must be updated for the v6.8.0 constructor: the $entity parameter is removed and the remaining parameters reorder.

For ProductListingResult:

  • Build it with the new ProductListingResult::fromSearchResult(...) factory instead of createFrom + setters. The factory signature is stable across the v6.8.0 cut.
  • The listing state ($sorting, $currentFilters, $availableSortings, $streamId, $page, $limit) stays mutable: listing processors modify the result after construction by design, so addCurrentFilter(), setSorting(), setAvailableSortings(), setStreamId(), setPage(), and setLimit() remain supported. Only the surface inherited from EntitySearchResult goes away.

For ProductReviewResult:

  • Build it with the new ProductReviewResult::fromSearchResult(...) factory instead of createFrom + setters.
  • The class becomes fully immutable: $matrix, $productId, $customerReview, $totalReviewsInCurrentLanguage, and $parentId become readonly, and the setters (setMatrix(), setProductId(), setCustomerReview(), setTotalReviewsInCurrentLanguage(), setParentId()) will be removed — pass the values to fromSearchResult() instead.

Faster category creation and editing

Creating or editing a single category no longer re-indexes unrelated categories. Previously, adding a sub-category or changing a single field (such as the name) of one category re-indexed the whole branch — every sibling and the parent's entire subtree — which produced a large number of SQL queries and noticeably slow saves in shops with many categories. A category write now only re-indexes the affected category and its own descendants (plus the parent's child count when a category is created, deleted, or moved to a different parent). Merchants with large category trees will see significantly faster saving in the Categories module and lower database load.

For extension developers: as a consequence, the CategoryIndexerEvent is now dispatched with a smaller id set for these writes. If you subscribe to it and previously relied on receiving sibling categories that were not actually affected by the write, adjust your listener to resolve the categories it needs explicitly.

Rule Builder: "all / at least one" toggle is now config-driven

Whether a line item condition offers the "all / at least one" match-all toggle is now decided by the condition's getConfig() (isMatchAny) instead of being shown for every line item condition.

Rule Builder: line item purchase price uses a net/gross type field

LineItemPurchasePriceRule (cartLineItemPurchasePrice) now stores the price type as a type field (gross / net) instead of an isNet boolean, aligning it with the generic rule configuration and rendering it via sw-condition-generic.

Not-null translation columns accept falsy defaults

DefinitionValidator no longer reports a not-null translation column as missing a default when the column has a falsy but set default such as 0, '0', or ''. Only a null default is now treated as missing. This removes false positives for plugin entity definitions that use such defaults.

OneToMany association limit now respects sort order across joined tables

When a paginated OneToMany association was loaded with both setLimit() and a sort on a field belonging to a joined entity (i.e. product.media.position), the limit could select the wrong rows.

No changes to calling code are required, but the sorting of associations with a limit may change for OneToMany associations, as they now reliably return the top-N rows in the requested order.

Added --no-scaffold flag to plugin:create command

The bin/console plugin:create command now accepts a --no-scaffold flag that skips all optional scaffold generators, producing only the minimal required plugin skeleton.

bash
bin/console plugin:create MyPlugin MyNamespace --no-scaffold

Dynamic product groups can keep matching variants ungrouped

Now, product streams have a new boolean field displayAsGroup and a corresponding Administration toggle "Keep matching variants grouped" on the dynamic product group detail page. When displayAsGroup is disabled, matching variants are returned and rendered individually instead of being grouped or remapped.

The new database field product_stream.display_as_group defaults to 1, so existing product streams keep the previous grouped behavior after migration unless they are changed explicitly. Also, ProductStreamBuilderInterface and buildFilters() are deprecated and will be removed in v6.8.0.0; use the new AbstractProductStreamBuilder::enrichCriteria() as the primary extension point instead.

New Criteria::excludeFields() for reduced entity reads

Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria::excludeFields() is a denylist counterpart to addFields(): it loads the full, typed entity (and its associations) but omits the named storage columns from the read — useful to skip heavy, off-page columns (for example a large product.description) without loading them for every row.

Unlike addFields(), which is an allowlist that returns PartialEntity instances and drops everything not listed, excludeFields() returns the regular entity type (e.g. ProductEntity) with the excluded properties left at their default value. Typed getters, instanceof checks and *.loaded subscribers therefore keep working. It cannot be combined with addFields() on the same criteria, and required or write-protected top-level fields cannot be excluded — attempting to exclude one (e.g. stock) or an unknown field throws a DataAbstractionLayerException.

Product listings now use this instead of the previous field allowlist: when reduced listing loading (core.listing.partialDataLoading) is enabled, listings load full product entities minus description, keywords and customSearchKeywords, so customFields, associations and other data remain available. As a result the Shopware\Core\Content\Product\SalesChannel\Listing\ProductListingLoader::PARTIAL_LISTING_FIELDS constant is deprecated and will be removed in v6.8.0.0.

Mail template simulation supports form data

Mail template simulation now provides sample data for the contactFormData, reviewFormData and revocationRequestFormData variables, so simulating these form templates no longer fails.

Extensions that add their own forms can supply sample data for their form variables too. Declare the variable as Shopware\Core\Framework\Event\EventData\FormDataObjectType (instead of a schemaless ObjectType) in the event's getAvailableData(), and provide the data via the new Shopware\Core\Content\MailTemplate\Service\Event\MailDataSimulatorFormDataEvent:

php
public function provideFormData(MailDataSimulatorFormDataEvent $event): void
{
    if ($event->flowEventName === 'my_custom_form.send' && $event->variableName === 'myCustomFormData') {
        $event->setData(['field' => 'value']);
    }
}

Deprecated UnmappedFieldException in the DBAL sub-namespace

Shopware\Core\Framework\DataAbstractionLayer\Dbal\Exception\UnmappedFieldException is deprecated in favor of the new Shopware\Core\Framework\DataAbstractionLayer\Exception\UnmappedFieldException. The deprecated class keeps working and will be removed in Shopware 6.8.

DataAbstractionLayerException::unmappedField() returns the deprecated class while the v6.8.0.0 flag is off and the new class once it is active. Prepare your code now:

  • Switch your use and catch statements to the new Shopware\Core\Framework\DataAbstractionLayer\Exception\UnmappedFieldException.
  • While you still support the deprecated version, catch both classes, since they do not share a common parent.

Storefront

Deprecated type variable in address manager templates

The Twig variable type in the address manager modal templates (address-manager-modal-list.html.twig, address-manager-modal-create-address.html.twig, and address-manager-item.html.twig) is deprecated in favor of addressType. The old variable remains available during the transition and will be removed with Shopware 6.8. Themes and plugins that extend these templates should migrate to addressType.

Form validation messages use Storefront snippets

Validation errors rendered by the Storefront FormController for contact, newsletter, and revocation forms are now translated from the violation code through Shopware's snippet system. This ensures that the active Storefront language is used instead of Symfony's validator translation catalogue. Plugin authors using custom constraints in these forms should provide matching error.<violation-code> entries in Resources/snippet/storefront.<locale>.json.

Categories of type "link" are no longer excluded from SEO URL generation in NavigationPageSeoUrlRoute. Since link categories redirect to their configured target when opened, their own SEO URL (e.g. from an old bookmark, an external link, or a category that was switched from type "page" to "link") now resolves to that redirect instead of a 404. Categories of type "folder" remain excluded, and link categories are still omitted from the sitemap. Existing shops get the URLs (re)generated with the next category indexing, e.g. via bin/console dal:refresh:index.

robots.txt allows crawling product feed tracking URLs

The default storefront robots.txt now emits Allow: /*referringSalesChannel= alongside the existing Disallow: /*?. Product feed links (the sales-channel tracking feed used by agentic commerce) carry a referringSalesChannel query parameter; the blanket Disallow: /*? previously stopped Googlebot from crawling those landing pages, which caused Google Merchant Center to disapprove the products. The clean, parameter-free URL is still what gets indexed via the page's rel=canonical. Plugins that emit their own tracking parameters can add an equivalent Allow directive by subscribing to RobotsPageLoadedEvent.

Paginated storefront URLs now have unique canonical URLs

Storefront listing pages now include their page number in the canonical URL when pagination is used. This ensures that each paginated page has its own canonical URL, allowing search engines to index the pages in the sequence correctly.

Deprecated AbstractDomainLoader::load() in favor of loadDomains()

Shopware\Storefront\Framework\Routing\AbstractDomainLoader::load() is deprecated and will be removed with Shopware 6.8. Use the new loadDomains() method instead, which returns a Shopware\Storefront\Framework\Routing\Struct\DomainCollection of Shopware\Storefront\Framework\Routing\Struct\DomainStruct objects, keyed by domain URL.

loadDomains() is already available: its default implementation builds the collection from load() for backward compatibility, but will become abstract with 6.8. If you decorate AbstractDomainLoader, implement loadDomains() in your decorator. If you consume the result, look up entries via the collection (e.g. $domains->get($url)) and access the values as objects (e.g. $domain->url) instead of array keys ($domains[$url]['url']).

The storefront FormFieldToggle plugin now supports optionally updating a related button label when the toggle value changes.

This is useful for dynamic forms (for example subscribe vs unsubscribe flows) where hidden/visible field groups and the submit action label should stay in sync without introducing a dedicated custom plugin.

For extension and theme developers, two optional data attributes are available on the controlling field:

  • data-form-field-toggle-button-target: CSS selector for the related button.
  • data-form-field-toggle-button-text: Alternate button text that is applied when the toggle target is hidden.

If those attributes are not provided, FormFieldToggle behaves exactly as before.

API

Store API OpenAPI: JSON schema files take precedence over generated entity schemas

The StoreApiGenerator now checks whether a component schema already exists in the JSON schema files before using the OpenAPI schema generated from the PHP EntityDefinition. If a match is found, the PHP-generated OpenAPI component is ignored.

JSON schema files are now the sole source of truth for any entity they define. Properties, required fields, and other schema details from the PHP EntityDefinition will not be merged into the JSON schema.

If you maintain a bundle that provides both a PHP EntityDefinition and a JSON schema file under Resources/Schema/StoreApi/components/schemas/ for the same entity, ensure the JSON file is complete. The PHP EntityDefinition remains responsible for DAL and internal entity handling.

See the JSON as the Source of Truth for API Schema RFC for the full rationale and roadmap.

Purchase prices removed from Store API order line item payloads

Order line item JSON serialization no longer exposes product purchasePrices in the payload returned by Store API order responses. Purchase prices are confidential cost data and were not intended to be part of customer-facing APIs. Headless storefronts, apps, and integrations must stop reading orders.elements[].lineItems[].payload.purchasePrices; there is no Store API replacement for this confidential value. At PHP level, the raw payload remains available through LineItem::getPayload(), LineItem::getPayloadValue(), and OrderLineItemEntity::getPayload(), but LineItem::jsonSerialize() and OrderLineItemEntity::jsonSerialize() omit protected purchase prices from API output. Because the field is removed during JSON serialization, the change applies to existing and new orders without rewriting historical order payloads.

Image CMS element no longer emits a default min-height outside cover mode

The min-height of the image CMS element (cms_slot of type image) is now only meaningful in the cover display mode. New image elements default to an empty minHeight instead of 340px, the Administration clears the value when switching away from cover, and the Storefront only applies a min-height (falling back to 340px) when the display mode is cover. This fixes a forced height being applied in the standard and stretch display modes.

For the Storefront this is purely a rendering fix. Headless and Composable Frontends that read config.minHeight.value from the Store API should gate the value on config.displayMode.value === 'cover', because relying on the previous 340px default in non-cover modes no longer reflects the rendered behaviour. Existing image elements keep their stored minHeight; only newly created elements use the new empty default.

DAL write event listeners no longer expand API ACL requirements

DAL post-write events such as EntityWrittenContainerEvent and entity-specific .written events are now dispatched in system scope after Admin API and Sync API writes, while preserving the original context source.

This matters for plugins that subscribe to core write events and update their own entities as a side effect. Previously, a listener on an event such as product.written still ran in the CRUD context of the triggering API request. When that listener wrote an extension-owned entity, the API user also needed permissions for that extension entity, even though the submitted request only changed products. Activating such a plugin could therefore change the required ACL permissions for existing Admin API or Sync API clients.

With this change, listener-side DAL writes are treated as trusted system-side follow-up work of the original write. API consumers only need the privileges required for the submitted write payload; plugin-internal denormalization, synchronization, indexing, or bookkeeping writes performed from DAL write listeners no longer expand the caller's ACL requirements.

Extension authors can still inspect who triggered the write via $event->getContext()->getSource(). If a listener intentionally wants to make a side effect depend on the triggering user or integration, it should check the source explicitly instead of relying on $event->getContext()->getScope() being Context::USER_SCOPE. No adoption is required for normal write-event listeners; remove any extra API permission requirements that only existed to satisfy listener-internal entity writes.

Private media visibility is not implicitly widened by this change. During DAL write-event dispatch, Shopware marks the context with Context::SYSTEM_SCOPE_DAL_WRITE_EVENT so private media searches still apply normal visibility restrictions. If a listener intentionally needs private media access, wrap that specific read in $context->scope(Context::SYSTEM_SCOPE, ...); explicit system-scope reads continue to opt in to private media visibility.

Manage translation downloads via the Admin API

Translation management — previously only possible through the translation:list, translation:install, and translation:update CLI commands — is now available through the Admin API, so it can be driven from the Administration without shell access:

  • GET /api/_action/translation/list — lists every configured locale with its locally installed metadata ({ total, items: [{ locale, name, lastUpdate, progress }] }).
  • POST /api/_action/translation/install — downloads and installs translations for the given locales (or all configured locales when all is true); created languages are activated unless activate is false. Returns { updated, skipped, unavailable }, where unavailable lists requested locales that have no translation available.
  • POST /api/_action/translation/update — updates all installed translations. Returns { updated, skipped, unavailable }.
  • DELETE /api/_action/translation/{locale} — removes the downloaded translation files and the metadata entry for a locale. The associated language, locale, and snippet_set records are left untouched and remain manageable through their regular entity endpoints.

The routes are guarded by the new system:translation ACL privilege (read for listing, create for install, update for update, delete for uninstall).

install and update process the requested locales synchronously during the request, downloading each locale's snippet files in turn. Installing many locales at once — in particular all: true, which covers every configured locale — can therefore take a while, and the operation is not atomic: if one locale fails, the locales processed before it remain installed.

Two events are dispatched from the underlying services (so they fire for both the Admin API and the translation:* CLI commands), giving extensions a targeted hook instead of having to filter generic DAL write events:

  • Shopware\Core\System\Snippet\Event\TranslationLoadedEvent — after a locale's translations are downloaded and installed (carries the locale and the Context).
  • Shopware\Core\System\Snippet\Event\TranslationRemovedEvent — after a locale's downloaded files and metadata entry are removed (carries the locale).

Download media files via the Admin API

The Admin API provides GET /api/_action/media/{mediaId}/download to download the binary file of a media entity. Depending on the configured media storage and download strategy, the route may either stream the file from Shopware or respond with a redirect to the resolved download URL.

For Administration clients that need to decide whether to trigger a direct browser download or fall back to an authenticated blob request, the Admin API now also provides GET /api/_action/media/{mediaId}/download/prepare. The route is guarded by the existing media:read ACL privilege and returns a small JSON payload describing whether the client should use an external URL or perform the authenticated blob download through Shopware.

App System

Deprecation of inline <custom-fields> in manifest.xml

Defining custom fields inline in manifest.xml via the <custom-fields> element is deprecated. Use a separate Resources/config/custom-fields.xml file instead. The inline definition will be removed in v6.8.0.

When an app has a Resources/config/custom-fields.xml file, it takes priority over the inline manifest definition. If only the inline definition exists, a deprecation warning is triggered.

Tax provider priority is preserved across app updates

An app tax provider's priority is now only seeded from the manifest when the provider is first installed. App updates no longer touch the priority, so the merchant's manual ordering is retained.

Hosting & Configuration

New GENERATE_SOURCEMAPS environment variable for production builds

A new GENERATE_SOURCEMAPS environment variable controls whether JavaScript sourcemaps are emitted during production builds. Set it to true to generate sourcemaps when NODE_ENV=production; omit it or set it to any other value to keep the default behaviour (no sourcemaps in production).

This applies to the Storefront webpack build and all Vite builds (Administration core, Administration extension plugins, and Storefront components).

In non-production environments sourcemaps are always generated regardless of this variable.

bash
GENERATE_SOURCEMAPS=true NODE_ENV=production composer build:js:admin
GENERATE_SOURCEMAPS=true NODE_ENV=production composer build:js:storefront

Administration

Reworked search behaviour options

The "Search behaviour" card in Settings > Search presents the search mode as "Broad search (OR)" and "Exact search (AND)" with short one-line descriptions, replacing the previous "OR"/"AND" labels with example texts. The broad option is now listed first; the stored configuration (product_search_config.andLogic) and the template blocks are unchanged. Extensions that override the mode selection (e.g. Advanced Search) can swap the offered options based on their own configuration.

Digital product upload validation uses backend private media metadata

Administration upload validation for digital products now derives private upload MIME metadata from the effective backend private extension allowlist. Extensions added through shopware.filesystem.private_allowed_extensions or MediaFileExtensionWhitelistEvent are reflected in /api/_info/config and in the digital-product upload UI.

Snippet inheritance from JSON language files

The snippet detail page (Settings > Snippets) now indicates if a snippet is defined in a JSON language file and if it has been changed, displays its original value. Additionally, editors can now restore inheritance from the underlying JSON file.

Clicking the "restore inheritance" icon on an overridden field marks the database record for deletion upon saving. This allows the snippet to fall back to the JSON file value and ensures it stays synchronized with any future updates made to the language file.

Block additions and renamings

Due to missing blocks and inappropriate block names, the following templates have received new blocks and/or contain blocks which have been deprecated and will be removed in v6.8.0. Use the respective replacements instead:

sw-cms-el-config-buy-box.html.twig

Deprecated -> Replacement:

  • sw_cms_element_buy_box_config_product_variant_label -> sw_cms_element_buy_box_config_product_selection_label
  • sw_entity_single_select_base_results_list_result_label -> sw_cms_element_buy_box_config_product_select_result_item_inner
sw-cms-el-config-cross-selling.html.twig

Deprecated -> Replacement:

  • sw_entity_single_select_variant_selected_item -> sw_cms_element_cross_selling_config_content_products_selection_label
  • sw_entity_single_select_variant_result_item -> sw_cms_element_cross_selling_config_content_products_select_result_item
  • sw_entity_single_select_base_results_list_result_label -> sw_cms_element_cross_selling_config_content_products_select_result_item_inner
sw-cms-el-config-product-box.html.twig

Added:

  • sw_cms_element_product_box_config_product_selection_label
  • sw_cms_element_product_box_config_product_select_result_item

Deprecated -> Replacement:

  • sw_entity_single_select_base_results_list_result_label -> sw_cms_element_product_box_config_product_select_result_item_inner
sw-cms-el-config-product-description-reviews.html.twig

Deprecated -> Replacement:

  • sw_entity_single_select_variant_selected_item -> sw_cms_element_product_description_reviews_config_product_selection_label
  • sw_entity_single_select_variant_result_item -> sw_cms_element_product_description_reviews_config_product_select_result_item
  • sw_entity_single_select_base_results_list_result_label -> sw_cms_element_product_description_reviews_config_product_select_result_item_inner
sw-cms-el-config-product-slider.html.twig

Added:

  • sw_cms_element_product_slider_config_content_products_selection_label
  • sw_cms_element_product_slider_config_content_products_select_result_item

Deprecated -> Replacement:

  • sw_entity_single_select_base_results_list_result_label -> sw_cms_element_product_slider_config_content_products_select_result_item_inner
sw-product-cross-selling-assignment.html.twig

Added:

  • sw_product_cross_selling_assignment_select_result_item

Deprecated -> Replacement:

  • sw_entity_single_select_base_results_list_result_label -> sw_product_cross_selling_assignment_select_result_item_inner

Fixed bugs

  • Fixed product stream creation where the "Display as group" toggle would not be saved correctly and the Save button kept spinning indefinitely (#18619)
  • Fixed an issue where cloning email templates would always fail due to a write constraint violation on the wasModifiedByUser field (#18251)
  • Fixed currency display on the order finish page when viewing orders in a different currency than the storefront default (#18294)
  • Fixed clearing values in sw-single-select components, ensuring that custom field selections are correctly persisted when cleared using the ✕ icon (#18075)
  • Fixed an issue where resizing the browser window would cause the image slider to switch to a different slide (#18188)

See all fixed bugs in this release: https://github.com/shopware/shopware/milestone/41?closed=1

Credits

Thanks to all diligent friends for helping us make Shopware better and better with each pull request!

See all contributors on this page: https://github.com/shopware/shopware/releases/tag/v6.7.13.0#Contributors

More resources

Get in touch

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

Was this page helpful?
UnsatisfiedSatisfied
Be the first to vote!
0.0 / 5  (0 votes)