<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Spryker Documentation</title>
        <description>Spryker documentation center.</description>
        <link>https://docs.spryker.com/</link>
        <atom:link href="https://docs.spryker.com/feed.xml" rel="self" type="application/rss+xml"/>
        <lastBuildDate>Thu, 06 Aug 2026 12:44:30 +0000</lastBuildDate>
        <generator>Jekyll v4.2.2</generator>
        
        
        <item>
            <title>Test the asynchronous API</title>
            <description>This document describes how to set up and run AsyncAPI tests.
We use the *Hello World* example throughout this document. All code references the Hello World App and the `Pyz` project namespace. When you set up and run the tests for a different project namespace or module, adjust the names accordingly.

## Prerequisites

&lt;!--Either you followed the instructions on how to Create an App or you have an already created App in place.--&gt;

Make sure the following prerequisites are met:

1. Spryker Testify version 3.50.0 or later is installed. The AsyncAPI SDK is required by this package, however, you don&apos;t need to install it manually.
- Verify the installation status and version of Spryker Testify:

  ```bash
  composer info spryker/testify
  ```

- Install Spryker Testify:

  ```bash
  composer require --dev &quot;spryker/testify:^3.50.0&quot;
  ```

- Update Spryker Testify:

  ```bash
  composer update &quot;spryker/testify:^3.50.0&quot;
  ```

2. Spryker Testify AsyncAPI version 0.1.1 or later is installed. The AsyncAPI SDK is required by this package, however, you don&apos;t need to install it manually.
- Verify the installation status and version of Spryker Testify AsyncAPI:

  ```bash
  composer info spryker/testify-async-api
  ```

- Install Spryker Testify AsyncAPI:

  ```bash
  composer require --dev &quot;spryker/testify-async-api:^0.1.1&quot;
  ```

- Update Spryker Testify AsyncAPI:

  ```bash
  composer update &quot;spryker/testify-async-api:^0.1.1&quot;
  ```

3. Spryks version 0.5.2 or later is installed.
- Verify the installation status and version of Spryks:

  ``` bash
  composer info spryker-sdk/spryk
  ```

- Install Spryks:

  ```bash
  composer require --dev &quot;spryker-sdk/spryk:^0.5.2&quot;
  ```

- Update Spryks:

  ```bash
  composer update &quot;spryker-sdk/spryk:^0.5.2&quot;
  ```

4. There is a valid AsyncAPI schema file in `resources/api/asyncapi.yml`. To create the file, you can use the example provided in Hello World App AsyncAPI.

## Testing the asynchronous API

Testing the asynchronous API implies that all schema files are tested to ensure that they align with the code that handles or produces messages. Each module that has an AsyncAPI schema file must have a dedicated test suite.

To test the asynchronous API, follow these steps:

### 1. Generate the code

To generate the code, you need to provide a valid schema file within your app. The schema file must reside in `resources/api/asyncapi.yml`. In the following example, we use the file provided in the Hello World App AsyncAPI, which you can also use as a starting point for your project.
After you have added the schema file, run the code generator for it using the following command:

```bash
docker/sdk cli vendor/bin/asyncapi code:asyncapi:generate -o Pyz
```

This command adds relevant modules and tests to your project to get you started with the asynchronous API in the `src/` and `tests/` directories.

To verify the introduced changes, check the `src/` and `tests/` directories.

### 2. Build Codeception

Run the following Codeception build command:

```bash
docker/sdk cli vendor/bin/codecept build -c tests/PyzTest/AsyncApi/HelloWorld
```

Not everything can be automatically generated with these commands. You also need to update the Codeception configuration and the project configuration as described in the following sections.

### 3. Update the Codeception configuration

Open the created Codeception configuration file at `tests/PyzTest/AsyncApi/HelloWorld/codeception.yml` and add the generated handlers to the configuration of `AsyncApiHelper`.

The file should contain the following section:

```yml
\Spryker\Zed\TestifyAsyncApi\Business\Codeception\Helper\AsyncApiHelper:
    asyncapi: resources/api/asyncapi.yml
    handlers:
        - \Pyz\Zed\HelloWorld\Communication\Plugin\MessageBroker\UserCreatedMessageHandlerPlugin
```

Depending on your schema file, you need to add your specific handlers. All handlers are located in the `src/Pyz/Zed/HelloWorld/Communication/Plugin/MessageBroker` directory. Add the class name of each handler to your Codeception configuration.

### 4. Update the project configuration

When testing the asynchronous API, all messages must be sent to the local message broker transport. This should only happen when you test the API with automated tests.

Add the following configuration to the `config/Shared/config_local.php` file:

```php
use Spryker\Shared\MessageBroker\MessageBrokerConstants;

$config[MessageBrokerConstants::IS_ENABLED] = true;
$config[MessageBrokerConstants::MESSAGE_TO_CHANNEL_MAP] = [
    &apos;*&apos; =&gt; &apos;test-channel&apos;,
];

$config[MessageBrokerConstants::CHANNEL_TO_TRANSPORT_MAP] = [
    &apos;test-channel&apos; =&gt; &apos;local&apos;,
];
```

{% info_block warningBox &quot;Warning&quot; %}

This is a very generic configuration and shouldn&apos;t be used in a production environment.

{% endinfo_block %}

### 5. Run the tests

Run the tests using the following command:

```bash
docker/sdk testing vendor/bin/codecept run -c tests/PyzTest/AsyncApi/HelloWorld
```

Once the testing process is complete, you get the result of each individual test.

## Example test methods

This section lists some example methods and explains what and how they test.

### Handling messages

Here is the example of handling the messages:

```php
public function testUserCreatedMessageCreatesAUserEntity(): void
{
    // Arrange
    $userCreatedTransfer = $this-&gt;tester-&gt;haveUserCreatedTransfer();

    // Act
    $this-&gt;tester-&gt;runMessageReceiveTest($userCreatedTransfer, &apos;user-events&apos;);

    // Assert
    $this-&gt;tester-&gt;assert...(...);
}
```

In the `Arrange` section, implement the code to allow for a message transfer that you expect to receive from another application.

In the `Act` section, call `runMessageReceiveTest` in `AsyncApiHelper`. The `runMessageReceiveTest` method takes the message that you expect to receive as its first argument and the channel name where you expect the message to come through as its second argument. Internally, the message and the channel name are validated against your `asyncapi.yml` schema file to ensure that both meet the definition.

In the `Assert` section, you make assertions based on your business logic. For example, you might verify the existence of a database entry after the message has been processed.

The underlying `AsyncApiHelper` ensures the following inside the `runMessageReceiveTest` method:

- The message handler can handle the message.
- The expected channel name exists in the schema file.
- The expected message name exists in the schema file.
- The message contains all required attributes defined in the schema file.

The `AsyncApiHelper` also executes the handler with the passed message. After the `runMessageReceiveTest` method execution, you need to make your assertions, such as verifying that a specific change was made in your database after processing the message.
The only remaining tasks for you are to implement the business logic, update the tests according to your business logic, and then run the tests.
To run the tests, use the following command:

```bash
vendor/bin/codecept run -c tests/PyzTest/AsyncApi/HelloWorld/
```

### Publishing messages

Here is the example of publishing the messages:

```php
public function testGreetUserMessageIsEmittedWhenUserWasStoredInTheDatabase(): void
{
    // Arrange
    $expectedGreetUserTransfer = $this-&gt;tester-&gt;haveGreetUserTransfer();

    // Act
    $this-&gt;tester-&gt;getFacade()-&gt;saveUser(...);

    // Assert
    $this-&gt;tester-&gt;assertMessageWasEmittedOnChannel($expectedGreetUserTransfer, &apos;user-commands&apos;);
}
```

In the `Arrange` section, prepare a message transfer that you expect to be sent once your business logic is executed.

In the `Act` section, call your business logic. For example, you could call a facade method where you expect the message to be sent.

In the `Assert` section, call the `assertMessageWasEmittedOnChannel` method with the expected message you created in the `Arrange` section. The first argument is the message that you expect to be sent, and the second argument is the channel name through which you expect the message to be sent. Internally, the message and the channel name are validated against your `asyncapi.yml` schema file to ensure that both meet the definition.

The underlying `AsyncApiHelper` ensures the following inside the `assertMessageWasEmittedOnChannel` method:

- The expected channel name exists in the schema file.
- The expected message name exists in the schema file.
- The message was sent with all required attributes defined in the schema file.
</description>
            <pubDate>Thu, 06 Aug 2026 11:40:23 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/guidelines/testing-guidelines/executing-tests/test-the-asynchronous-api.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/guidelines/testing-guidelines/executing-tests/test-the-asynchronous-api.html</guid>
            
            
        </item>
        
        <item>
            <title>Marketplace Product Options feature: Domain model and relationships</title>
            <description>The *Marketplace Product Options* feature lets merchants create their product option groups and values. Currently, you can [import product options](/docs/pbc/all/product-information-management/latest/marketplace/import-and-export-data/import-file-details-merchant-product-option-group.csv.html) where you specify the merchant reference.

## Module dependency graph

The following diagram illustrates the dependencies between the modules for the *Marketplace Product Options* feature.

![Module Dependency Graph](https://confluence-connect.gliffy.net/embed/image/d8882366-b2dd-4d6c-b401-01db47a00481.png?utm_medium=live&amp;utm_source=custom)

| NAME | DESCRIPTION |
| --- | --- |
| [MerchantProductOption](https://github.com/spryker/merchant-product-option) | Provides merchant product option main business logic and persistence. |
| [MerchantProductOptionDataImport](https://github.com/spryker/merchant-product-option-data-import) | Provides data import functionality for merchant product options. |
| [MerchantProductOptionStorage](https://github.com/spryker/merchant-product-option-storage) | Provides publish and sync functionality for merchant product options. |
| [MerchantProductOptionGui](https://github.com/spryker/merchant-product-option-gui) | Provides Back Office UI for merchant product options management. |
| [ProductOption](https://github.com/spryker/product-option) | Provides additional layer of optional items that can be sold with the actual product. |
| [ProductOptionStorage](https://github.com/spryker/product-option-storage) | Provides publish and sync functionality for product options. |
| [ProductOptionWidget](https://github.com/spryker-shop/product-option-widget) | Provides widgets for displaying product options. |

## Domain model

The following schema illustrates the Marketplace Product Options domain model:

![Domain Model](https://confluence-connect.gliffy.net/embed/image/90a0e5bc-a0d9-4cb2-a215-c5d08a786115.png?utm_medium=live&amp;utm_source=custom)

                                                                                                                                                      |
</description>
            <pubDate>Thu, 06 Aug 2026 11:40:23 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/product-information-management/latest/marketplace/domain-model-and-relationships/marketplace-product-options-feature-domain-model-and-relationships.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/product-information-management/latest/marketplace/domain-model-and-relationships/marketplace-product-options-feature-domain-model-and-relationships.html</guid>
            
            
        </item>
        
        <item>
            <title>Marketplace Product Offer Prices feature: Domain model and relationships</title>
            <description>This document provides technical details about the Marketplace Product Offer Prices feature.

## Module dependency graph

The following diagram illustrates the dependencies between the modules for the *Marketplace Product Offer Prices* feature.

![Entity diagram](https://confluence-connect.gliffy.net/embed/image/f128877d-eb61-4d87-b1af-5f166eb45c45.png?utm_medium=live&amp;utm_source=confluence)

| MODULE     | DESCRIPTION                |
|------------|----------------------------|
| PriceProductOffer | Provides product offer price-related functionality, price persistence, current price resolvers per currency/price mode.   |
| PriceProductOfferDataImport | Imports data for product offer prices.    |
| PriceProductOfferGui | Back Office UI Interface for managing prices for product offers.    |
| PriceProductOfferStorage | Provides functionality to store data about product offer prices in the storage.   |
| PriceProductOfferVolume | Provides functionality to handle volume prices for product offers.    |
| PriceProductOfferVolumeGui | Back Office UI Interface for managing volume prices for product offers.    |
| PriceProductOfferExtension | Provides plugin interfaces for extending `PriceProductOffer` module functionality.   |
| PriceProductOfferStorageExtension | Provides plugin interfaces used by Price Product Offer Storage bundle.    |
| PriceProductOfferVolumesRestApi | Provides plugins to add `product-offer-volume-prices` to the `product-offer-prices`.   |
| ProductOfferPricesRestApi | Provides Rest API endpoints to manage product offer prices.   |
| ProductOfferPricesRestApiExtension | Provides plugin interfaces for extending the `ProductOfferPricesRestApi` module.    |
| Price | Handles product pricing and provides plugins for products to populate prices.  |
| PriceProduct | Provides product price-related functionality, price persistence, current price resolvers per currency/price mode.    |
| PriceProductStorage | Provides functionality to store data about product prices in the storage.    |
| PriceProductVolume | Provides functionality to handle volume prices for products.  |
| ProductOffer | Provides the core functionality for product offer features.   |

## Domain model

The following schema illustrates the Marketplace Product Offer Prices domain model:

![Entity diagram](https://confluence-connect.gliffy.net/embed/image/0ad490bb-f21f-4e4a-b6eb-e0102a8c7b42.png?utm_medium=live&amp;utm_source=confluence)
</description>
            <pubDate>Thu, 06 Aug 2026 11:40:23 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/price-management/latest/marketplace/domain-model-and-relationships/marketplace-product-offer-prices-feature-domain-model-and-relationships.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/price-management/latest/marketplace/domain-model-and-relationships/marketplace-product-offer-prices-feature-domain-model-and-relationships.html</guid>
            
            
        </item>
        
        <item>
            <title>Integrating with Spryker OMS</title>
            <description>Order Management System (OMS) in Spryker is a built-in workflow engine that manages the lifecycle of an order - from placement to delivery. It defines each step (for example payment, shipping, cancellation) as part of a process, with clear transitions and conditions.

For third-party integrations, you can primarily use OMS for event-driven and API-driven integrations.

## Event-driven integration (OMS as the source)

- OMS can be used to publish events (for example `Order.Paid`, `Order.Shipped`) when an order transitions to a specific state.
- Your third-party integration can then subscribe to these events (for example via a message queue like RabbitMQ) to trigger actions in an external system (for example update ERP, notify logistics partner, send customer email).

## API-driven integration (OMS as the target)

- You can expose Glue API endpoints that trigger specific OMS commands or state transitions (for example set order status to shipped, initiate return).
- This allows external systems (for example a Warehouse Management System, a Call Center application) to update the order status or trigger actions within Spryker&apos;s OMS.
- You can also extend the OMS process with custom states and transitions specifically designed for your third party&apos;s workflow.

## Further reading

For details on implementing creating OMS processes, see [Set up an Order Management System](/docs/dg/dev/backend-development/data-manipulation/set-up-an-order-management-system.html).</description>
            <pubDate>Thu, 06 Aug 2026 11:40:23 +0000</pubDate>
            <link>https://docs.spryker.com/docs/integrations/custom-building-integrations/integrating-with-spryker-oms/integrating-with-spryker-oms.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/integrations/custom-building-integrations/integrating-with-spryker-oms/integrating-with-spryker-oms.html</guid>
            
            
        </item>
        
        <item>
            <title>Back Office: Import merchant commissions</title>
            <description>&lt;p&gt;To import &lt;a href=&quot;/docs/pbc/all/merchant-management/latest/marketplace/marketplace-merchant-commission-feature-overview.html&quot;&gt;merchant commissions&lt;/a&gt;, follow the steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;In the Back Office, go to &lt;strong&gt;Marketplace &lt;span aria-label=&quot;and then&quot;&gt;&amp;gt;&lt;/span&gt;Merchant Commissions&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On the &lt;strong&gt;Merchant Commissions&lt;/strong&gt; page, click &lt;strong&gt;Import&lt;/strong&gt;.
This opens the &lt;strong&gt;Import Merchant Commissions&lt;/strong&gt; page.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Optional: If you don’t have a file with merchant commissions, to prepare it, in &lt;strong&gt;1 Download template&lt;/strong&gt;, click on &lt;strong&gt;commissions_template.csv&lt;/strong&gt;.
This downloads the file. Fill the file with merchant commission data using the template and the &lt;a href=&quot;#reference-information-merchant-commissions-import-file&quot;&gt;reference&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In &lt;strong&gt;2 Import CSV file&lt;/strong&gt;, click &lt;strong&gt;Choose File&lt;/strong&gt; and select the file with commissions on your machine.
This displays the name of the file next to &lt;strong&gt;Choose File&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To import the selected file with commissions, click &lt;strong&gt;Upload&lt;/strong&gt;.&lt;br /&gt;
This opens the &lt;strong&gt;Import Merchant Commissions&lt;/strong&gt; page. The imported merchant commissions are displayed in the table.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;reference-information-merchant-commissions-import-file&quot;&gt;Reference information: Merchant commissions import file&lt;/h2&gt;
&lt;section class=&apos;info-block &apos;&gt;&lt;i class=&apos;info-block__icon icon-info&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;
&lt;ul&gt;
&lt;li&gt;Some editors change the symbols based on your location. To make sure you can import commissions, we recommend using Google Sheets to edit import files.&lt;/li&gt;
&lt;li&gt;For an example of a filled out file, you can export the existing default commissions by clicking &lt;strong&gt;Export&lt;/strong&gt; on the &lt;strong&gt;Merchant Commissions&lt;/strong&gt; page.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;p&gt;This section explains how to fill out a merchant commission import file. For more information about the fields in this file, see &lt;a href=&quot;/docs/pbc/all/merchant-management/latest/marketplace/marketplace-merchant-commission-feature-overview.html&quot;&gt;Marketplace Merchant Commissions feature overview&lt;/a&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;COLUMN&lt;/th&gt;
&lt;th&gt;REQUIRED&lt;/th&gt;
&lt;th&gt;DATA EXAMPLE&lt;/th&gt;
&lt;th&gt;DATA EXPLANATION&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;key&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;mc1&lt;/td&gt;
&lt;td&gt;Unique identifier of the merchant commission.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;name&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;Merchant Commission 1&lt;/td&gt;
&lt;td&gt;Name of the merchant commission. Accepted length: 1 to 255 characters. Must be unique.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;description&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Description of the merchant commission.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;valid_from&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;2029-06-30 00:00:00&lt;/td&gt;
&lt;td&gt;Start date of the merchant commission validity in UTC.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;valid_to&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;2029-08-30 00:00:00&lt;/td&gt;
&lt;td&gt;End date of the merchant commission validity in UTC.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;is_active&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Defines if the merchant commission is active (1) or inactive (0).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;amount&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Commission in percentage. Accepts decimals—for example, &lt;code&gt;10.99&lt;/code&gt; means 10.99%. If &lt;code&gt;calculator_type_plugin&lt;/code&gt; is set to &lt;code&gt;fixed&lt;/code&gt;, &lt;code&gt;amount&lt;/code&gt; must be &lt;code&gt;0&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;calculator_type_plugin&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;percentage&lt;/td&gt;
&lt;td&gt;Defines how commission is calculated. By default, accepts &lt;code&gt;percentage&lt;/code&gt; and &lt;code&gt;fixed&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;group&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;primary&lt;/td&gt;
&lt;td&gt;Can be &lt;code&gt;primary&lt;/code&gt; or &lt;code&gt;secondary&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;priority&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Defines which commission to apply within a group. Priority is defined in ascending order starting from one.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;item_condition&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;item-price &amp;gt;= ‘500’ AND category IS IN ‘computer’&lt;/td&gt;
&lt;td&gt;Condition for the item. &lt;code&gt;500&lt;/code&gt; refers to $500 in this case.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;order_condition&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;price-mode = “GROSS_MODE”&lt;/td&gt;
&lt;td&gt;Condition for the order.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stores&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;AT,DE&lt;/td&gt;
&lt;td&gt;Defines the stores to apply the commission in. Accepts multiple values.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;merchants_allow_list&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;MER000002,MER000006&lt;/td&gt;
&lt;td&gt;One or more merchants to apply the commission to.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fixed_amount_configuration&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;EUR|0.5|0.5,CHF|0.5|0.5&lt;/td&gt;
&lt;td&gt;Defines fixed amount commission configuration if a fixed commission applies to each item in the order. Format: &lt;code&gt;CURRENCY|GROSS AMOUNT|NET AMOUNT&lt;/code&gt;. &lt;code&gt;0.5&lt;/code&gt; refers to 50 cents in this example.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</description>
            <pubDate>Thu, 06 Aug 2026 11:40:23 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/merchant-management/latest/marketplace/manage-in-the-back-office/back-office-import-merchant-commissions.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/merchant-management/latest/marketplace/manage-in-the-back-office/back-office-import-merchant-commissions.html</guid>
            
            
        </item>
        
        <item>
            <title>API Platform Testing</title>
            <description>This document describes how to write and run tests for your API Platform resources in your project.

## Overview

API Platform provides a comprehensive testing infrastructure built on top of:

- **Codeception**: Test framework for PHP
- **API Platform Test Client**: Specialized HTTP client for API testing
- **PHPUnit Assertions**: Rich set of assertion methods
- **Test Helpers**: Custom helpers for test data management

The testing infrastructure supports both Backend and Storefront API types with dedicated base classes and configuration.

## Test tiers

Tests split into two tiers by what they cover and what they cost. Put a test in the cheapest tier that can carry it.

| Tier | Covers | Stack | Cost |
|------|--------|-------|------|
| **1 — logic** | Provider and processor mapping, error-to-status mapping | Shared kernel, no database, no HTTP | ~0.4 ms per test, after a one-time kernel boot per process |
| **2 — integration** | Full CRUD, the real error surface, auth to 401/403, validation to 422, serialization and `?include=` compound documents | Booted kernel over SQLite, no containers | ~1 s per test |

Neither tier needs DockerSDK. Tier 2 runs the real client and facade, and dispatches the Client-to-Zed RPC in process to the `GatewayController` with the JSON wire round-trip preserved, against a SQLite database. Only OAuth token introspection is stubbed.

### Tier 1: logic

Extend `StorefrontApiTestCase` (or `BackendApiTestCase`). Register the collaborators you want to control, take the subject from the helper, and call `provide()` or `process()` directly:

```php
$wishlistTransfer = $this-&gt;tester-&gt;haveWishlistTransfer();
$this-&gt;tester-&gt;setService(
    WishlistClientInterface::class,
    $this-&gt;tester-&gt;createClientStub(WishlistClientInterface::class, [
        &apos;getWishlistByFilter&apos; =&gt; $this-&gt;tester-&gt;haveSuccessfulWishlistResponseTransfer($wishlistTransfer),
    ]),
);
$provider = $this-&gt;tester-&gt;getProvider(WishlistsStorefrontProvider::class);

$result = $provider-&gt;provide(
    $this-&gt;tester-&gt;getGetOperation(WishlistsStorefrontResource::class),
    [&apos;uuid&apos; =&gt; $wishlistTransfer-&gt;getUuid()],
    $this-&gt;tester-&gt;getAuthenticatedContext(),
);
```

`getProvider()` and `getProcessor()` resolve the subject from the container, so the test exercises the real service wiring and only the collaborators it names are stubbed. Everything else is the real service.

Two rules follow from that:

- **Call `setService()` before the first `getProvider()`, `getProcessor()`, `getTestKernel()` or `createClient()` in a test method.** Mocks are bound when the kernel is taken for that method, so registering an id afterwards has no effect.
- **A collaborator you want to control has to be registered.** There is no auto-doubling of unregistered constructor arguments.

The kernel is shared across a suite&apos;s methods and the container is reset between them, so it is built once per Codeception process rather than per test. Enable that in the suite&apos;s `codeception.yml`:

```yaml
- \SprykerTest\ApiPlatform\Helper\ApiPlatformHelper:
      mode: &apos;project&apos;
      apiType: &apos;Storefront&apos;
      bootOnce: true
      reuseApplicationContainer: true
```

### Tier 2: integration

Extend the same base class and drive real requests with `handleApiRequest()`, asserting on the response. Use this tier for anything that needs persisted data, the serialization envelope, or the real validation and auth surface.

### Prerequisites

Both tiers resolve services from the container, which needs Symfony&apos;s test container. `framework.test` is configured per application in `config/&lt;Application&gt;/packages/framework.php` and has to be on for the environment the lane runs in. Without it the suites fail with `Could not find service &quot;test.service_container&quot;`.

The suites also need generated code that is not in version control — transfers, Propel models, entity transfers and the API resources. Generate it once per checkout and again after any schema change. The container is compiled on first use and then cached, which takes roughly 45 seconds; that cost recurs only when configuration or generated resources change.

## Test architecture

### Test class hierarchy

```bash
AbstractApiTestCase (base class from core)
├── BackendApiTestCase (for Backend API tests)
└── StorefrontApiTestCase (for Storefront API tests)
```

### Key components

| Component | Purpose |
|-----------|---------|
| `AbstractApiTestCase` | Base class providing API Platform integration |
| `BackendApiTestCase` | Pre-configured for Backend API testing |
| `StorefrontApiTestCase` | Pre-configured for Storefront API testing |
| `ApiTestKernel` | Lightweight Symfony kernel for testing |
| `ApiTestAssertionsTrait` | API-specific assertions (from API Platform) |

### Test helper classes

The testing infrastructure provides specialized Codeception helpers to streamline test development:

| Helper Class | Purpose |
|--------------|---------|
| `BootstrapHelper` | Configures application plugin providers for test environments via codeception.yml. Allows different test suites to use different factory implementations without hardcoding dependencies in test infrastructure. |
| `ApiPlatformHelper` | Configures resource generation and cache lifecycle for the test kernel. Has two modes — `project` (default, preserves the compiled container for speed) and `core` (generates fresh resources per suite and cleans the container cache afterwards, used when testing the API Platform module itself). See [ApiPlatformHelper modes](#apiplatformhelper-modes). |
| `ApiPlatformConfigBuilder` | Provides a fluent interface for building test-specific API Platform configurations. Useful for creating isolated test scenarios with custom settings. |
| `ApiResourceGeneratorHelper` | Assists with testing resource generation functionality. Provides methods to generate test resources, validate generation output, and clean up generated files. |

These helpers are automatically available in your test cases through the Codeception actor and provide essential functionality for testing API Platform resources effectively.

## Setting up your test environment

### 1. Configure autoloading for generated test resources

Update your project-level `composer.json` to include the test API namespace:

`composer.json` (project root)

```json
{
    &quot;autoload-dev&quot;: {
        &quot;psr-4&quot;: {
            &quot;PyzTest\\&quot;: &quot;tests/PyzTest/&quot;,
            &quot;Generated\\TestApi\\&quot;: &quot;tests/_data/Api/&quot;
        }
    }
}
```

### 2. Optional: Configure application plugin providers

If your tests require application plugins to be registered (for example, service providers or middleware), configure the `BootstrapHelper` in your suite&apos;s `codeception.yml`:

`tests/PyzTest/Glue/Customer/BackendApi/codeception.yml`

```yaml
modules:
    enabled:
        - \SprykerTest\Shared\Testify\Helper\BootstrapHelper:
            applicationPluginProvider:
                class: Spryker\Glue\GlueBackendApiApplication\GlueBackendApiApplicationFactory
                method: getApplicationPlugins
```

For Storefront API tests, use the appropriate factory:

`tests/PyzTest/Glue/Customer/StorefrontApi/codeception.yml`

```yaml
modules:
    enabled:
        - \SprykerTest\Shared\Testify\Helper\BootstrapHelper:
            applicationPluginProvider:
                class: Spryker\Glue\GlueStorefrontApiApplication\GlueStorefrontApiApplicationFactory
                method: getApplicationPlugins
```

**Configuration options:**

- `class`: The fully qualified class name of the factory that provides application plugins
- `method`: The method name to call on the factory (typically `getApplicationPlugins`)

If no `applicationPluginProvider` is configured, the helper returns an empty array, and tests run without additional application plugins.

### 3. Create test directory structure

```bash
tests/
├── PyzTest/
│   └── Glue/
│       └── Customer/
│           ├── BackendApi/
│           │   ├── codeception.yml
│           │   └── CustomersBackendApiTest.php
│           └── StorefrontApi/
│               ├── codeception.yml
│               └── CustomersStorefrontApiTest.php
└── _data/
    └── Api/
        ├── Backend/
        │   └── CustomersBackendResource.php (generated)
        └── Storefront/
            └── CustomersStorefrontResource.php (generated)
```

### 4. Generate API resources for testing

The resources and the container are automatically generated right before the test suite runs.

#### Automatic resource generation and cleanup

The test infrastructure handles resource lifecycle automatically:

- **Generation** (core mode only): Test-specific API resources are generated into `tests/_data/Api/{ApiType}/` before each suite executes. In project mode, the helper instead validates that the project-generated resources already exist on disk.
- **Cleanup** (core mode only): The `ApiPlatformHelper` clears the compiled Symfony test kernel cache and the generated resources after the suite completes. Project mode deliberately skips this step so the compiled container can be reused across runs.
- **Mode selection**: Choose the mode in `codeception.yml` — see [ApiPlatformHelper modes](#apiplatformhelper-modes) below for the trade-offs.

This automation ensures that:
- Tests always run against the latest schema definitions
- No manual cache clearing is required between test runs
- Test failures related to stale cache are eliminated

## Writing Backend API tests

### Basic test structure

Backend API tests extend `BackendApiTestCase` and use the `BackendApiTester` tester which gets automatically injected into your tests by Codeception.

`tests/PyzTest/Glue/Customer/BackendApi/CustomersBackendApiTest.php`

```php
&lt;?php

namespace PyzTest\Glue\Customer\BackendApi;

use PyzTest\Glue\Customer\BackendApiTester;
use SprykerTest\Shared\ApiPlatform\Test\BackendApiTestCase;

/**
 * @group PyzTest
 * @group Glue
 * @group Customer
 * @group BackendApi
 * @group CustomersBackendApiTest
 */
class CustomersBackendApiTest extends BackendApiTestCase
{
    protected BackendApiTester $tester;

    public function testGivenValidDataWhenCreatingCustomerViaPostThenCustomerIsCreatedSuccessfully(): void
    {
        // Arrange
        $customerData = [
            &apos;email&apos; =&gt; &apos;john.doe@example.com&apos;,
            &apos;firstName&apos; =&gt; &apos;John&apos;,
            &apos;lastName&apos; =&gt; &apos;Doe&apos;,
        ];

        // Act
        static::createClient()-&gt;request(&apos;POST&apos;, &apos;/customers&apos;, [&apos;json&apos; =&gt; $customerData]);

        // Assert
        $this-&gt;assertResponseIsSuccessful();
        $this-&gt;assertResponseStatusCodeSame(201);
        $this-&gt;assertJsonContains([&apos;email&apos; =&gt; &apos;john.doe@example.com&apos;]);
        $this-&gt;assertJsonContains([&apos;firstName&apos; =&gt; &apos;John&apos;]);
        $this-&gt;assertJsonContains([&apos;lastName&apos; =&gt; &apos;Doe&apos;]);
    }
}
```

### Testing GET operations

#### Single resource

```php
public function testGivenExistingCustomerWhenRetrievingViaGetThenCustomerDataIsReturned(): void
{
    // Arrange
    $customerTransfer = $this-&gt;tester-&gt;haveCustomer([
        &apos;email&apos; =&gt; &apos;existing@example.com&apos;,
        &apos;firstName&apos; =&gt; &apos;Jane&apos;,
        &apos;lastName&apos; =&gt; &apos;Smith&apos;,
    ]);

    // Act
    static::createClient()-&gt;request(
        &apos;GET&apos;,
        sprintf(&apos;/customers/%s&apos;, $customerTransfer-&gt;getCustomerReference())
    );

    // Assert
    $this-&gt;assertResponseIsSuccessful();
    $this-&gt;assertJsonContains([&apos;email&apos; =&gt; &apos;existing@example.com&apos;]);
    $this-&gt;assertJsonContains([&apos;firstName&apos; =&gt; &apos;Jane&apos;]);
}
```

#### Collection with pagination

```php
public function testGivenMultipleCustomersWhenRetrievingCollectionViaGetThenAllCustomersAreReturned(): void
{
    // Arrange
    $this-&gt;tester-&gt;haveCustomer([&apos;email&apos; =&gt; &apos;customer1@example.com&apos;]);
    $this-&gt;tester-&gt;haveCustomer([&apos;email&apos; =&gt; &apos;customer2@example.com&apos;]);
    $this-&gt;tester-&gt;haveCustomer([&apos;email&apos; =&gt; &apos;customer3@example.com&apos;]);

    // Act
    static::createClient()-&gt;request(&apos;GET&apos;, &apos;/customers&apos;);

    // Assert
    $this-&gt;assertResponseIsSuccessful();
    $this-&gt;assertJsonContains([&apos;@type&apos; =&gt; &apos;Collection&apos;]);
    $this-&gt;assertJsonContains([&apos;totalItems&apos; =&gt; 3]);
}

public function testGivenPaginationParamsWhenRetrievingCollectionThenPaginatedResultsAreReturned(): void
{
    // Arrange
    for ($i = 1; $i &lt;= 15; $i++) {
        $this-&gt;tester-&gt;haveCustomer([&apos;email&apos; =&gt; sprintf(&apos;customer%d@example.com&apos;, $i)]);
    }

    // Act
    static::createClient()-&gt;request(&apos;GET&apos;, &apos;/customers?page=2&amp;itemsPerPage=5&apos;);

    // Assert
    $this-&gt;assertResponseIsSuccessful();
    $this-&gt;assertJsonContains([&apos;@type&apos; =&gt; &apos;Collection&apos;]);
    $this-&gt;assertJsonContains([&apos;view&apos; =&gt; [&apos;@id&apos; =&gt; &apos;/customers?page=2&amp;itemsPerPage=5&apos;]]);
}
```

### Testing POST operations

#### Successful creation

```php
public function testGivenValidDataWhenCreatingCustomerViaPostThenCustomerIsCreatedSuccessfully(): void
{
    // Arrange
    $customerData = [
        &apos;email&apos; =&gt; &apos;new.customer@example.com&apos;,
        &apos;firstName&apos; =&gt; &apos;New&apos;,
        &apos;lastName&apos; =&gt; &apos;Customer&apos;,
    ];

    // Act
    $response = static::createClient()-&gt;request(&apos;POST&apos;, &apos;/customers&apos;, [
        &apos;json&apos; =&gt; $customerData,
    ]);

    // Assert
    $this-&gt;assertResponseIsSuccessful();
    $this-&gt;assertResponseStatusCodeSame(201);
    $this-&gt;assertJsonContains($customerData);
    $this-&gt;assertResponseHeaderSame(&apos;Content-Type&apos;, &apos;application/ld+json; charset=utf-8&apos;);

    // Verify the resource was created and has an ID
    $responseData = $response-&gt;toArray();
    $this-&gt;assertArrayHasKey(&apos;customerReference&apos;, $responseData);
    $this-&gt;assertNotEmpty($responseData[&apos;customerReference&apos;]);
}
```

#### Validation errors

```php
public function testGivenInvalidDataWhenCreatingCustomerViaPostThenValidationErrorIsReturned(): void
{
    // Arrange
    $invalidCustomerData = [
        &apos;email&apos; =&gt; &apos;invalid-email&apos;,  // Invalid email format
        &apos;firstName&apos; =&gt; &apos;&apos;,            // Empty first name
    ];

    // Act
    static::createClient()-&gt;request(&apos;POST&apos;, &apos;/customers&apos;, [
        &apos;json&apos; =&gt; $invalidCustomerData,
    ]);

    // Assert
    $this-&gt;assertResponseStatusCodeSame(422);
    $this-&gt;assertResponseHeaderSame(&apos;Content-Type&apos;, &apos;application/ld+json; charset=utf-8&apos;);
    $this-&gt;assertJsonContains([&apos;@type&apos; =&gt; &apos;ConstraintViolationList&apos;]);
    $this-&gt;assertJsonContains([
        &apos;violations&apos; =&gt; [
            [&apos;propertyPath&apos; =&gt; &apos;email&apos;],
            [&apos;propertyPath&apos; =&gt; &apos;firstName&apos;],
            [&apos;propertyPath&apos; =&gt; &apos;lastName&apos;],
        ],
    ]);
}
```

#### Business rule violations

```php
public function testGivenDuplicateEmailWhenCreatingCustomerViaPostThenErrorIsReturned(): void
{
    // Arrange
    $this-&gt;tester-&gt;haveCustomer([&apos;email&apos; =&gt; &apos;duplicate@example.com&apos;]);

    $duplicateData = [
        &apos;email&apos; =&gt; &apos;duplicate@example.com&apos;,
        &apos;firstName&apos; =&gt; &apos;Duplicate&apos;,
        &apos;lastName&apos; =&gt; &apos;Customer&apos;,
    ];

    // Act
    static::createClient()-&gt;request(&apos;POST&apos;, &apos;/customers&apos;, [
        &apos;json&apos; =&gt; $duplicateData,
    ]);

    // Assert
    $this-&gt;assertResponseStatusCodeSame(422);
    $this-&gt;assertJsonContains([&apos;@type&apos; =&gt; &apos;Error&apos;]);
    $this-&gt;assertJsonContains([&apos;detail&apos; =&gt; &apos;Customer with this email already exists&apos;]);
}
```

### Testing PATCH operations

```php
public function testGivenExistingCustomerWhenUpdatingViaPatchThenCustomerIsUpdatedSuccessfully(): void
{
    // Arrange
    $customerTransfer = $this-&gt;tester-&gt;haveCustomer([
        &apos;email&apos; =&gt; &apos;update@example.com&apos;,
        &apos;firstName&apos; =&gt; &apos;Original&apos;,
        &apos;lastName&apos; =&gt; &apos;Name&apos;,
    ]);

    $updateData = [
        &apos;firstName&apos; =&gt; &apos;Updated&apos;,
        &apos;lastName&apos; =&gt; &apos;Name&apos;,
    ];

    // Act
    static::createClient()-&gt;request(
        &apos;PATCH&apos;,
        sprintf(&apos;/customers/%s&apos;, $customerTransfer-&gt;getCustomerReference()),
        [
            &apos;json&apos; =&gt; $updateData,
            &apos;headers&apos; =&gt; [
                &apos;Content-Type&apos; =&gt; &apos;application/merge-patch+json&apos;,
            ],
        ]
    );

    // Assert
    $this-&gt;assertResponseIsSuccessful();
    $this-&gt;assertJsonContains([&apos;firstName&apos; =&gt; &apos;Updated&apos;]);
    $this-&gt;assertJsonContains([&apos;email&apos; =&gt; &apos;update@example.com&apos;]); // Unchanged
}
```

### Testing DELETE operations

```php
public function testGivenExistingCustomerWhenDeletingViaDeleteThenCustomerIsDeletedSuccessfully(): void
{
    // Arrange
    $customerTransfer = $this-&gt;tester-&gt;haveCustomer([
        &apos;email&apos; =&gt; &apos;delete@example.com&apos;,
    ]);

    // Act
    static::createClient()-&gt;request(
        &apos;DELETE&apos;,
        sprintf(&apos;/customers/%s&apos;, $customerTransfer-&gt;getCustomerReference())
    );

    // Assert
    $this-&gt;assertResponseStatusCodeSame(204);
    $this-&gt;assertResponseHasNoContent();
}

public function testGivenNonExistentCustomerWhenDeletingViaDeleteThen404IsReturned(): void
{
    // Act
    static::createClient()-&gt;request(&apos;DELETE&apos;, &apos;/customers/NON-EXISTENT-REFERENCE&apos;);

    // Assert
    $this-&gt;assertResponseStatusCodeSame(404);
}
```

### Testing relationships

The relationships feature enables resources to include related resources via the `?include=` query parameter. For details on configuring relationships, see [Relationships](/docs/dg/dev/architecture/api-platform/relationships.html).

#### Testing include parameter

```php
public function testGivenCustomerWithAddressesWhenRequestingWithIncludeThenAddressesAreIncluded(): void
{
    // Arrange
    $customerTransfer = $this-&gt;tester-&gt;haveCustomer();
    $this-&gt;tester-&gt;haveAddress([&apos;customerReference&apos; =&gt; $customerTransfer-&gt;getCustomerReference()]);
    $this-&gt;tester-&gt;haveAddress([&apos;customerReference&apos; =&gt; $customerTransfer-&gt;getCustomerReference()]);

    // Act
    $response = static::createClient()-&gt;request(
        &apos;GET&apos;,
        sprintf(&apos;/customers/%s?include=addresses&apos;, $customerTransfer-&gt;getCustomerReference())
    );

    // Assert
    $this-&gt;assertResponseIsSuccessful();
    $data = $response-&gt;toArray();

    // Assert relationships section exists
    $this-&gt;assertArrayHasKey(&apos;relationships&apos;, $data[&apos;data&apos;]);
    $this-&gt;assertArrayHasKey(&apos;addresses&apos;, $data[&apos;data&apos;][&apos;relationships&apos;]);

    // Assert included section contains addresses
    $this-&gt;assertArrayHasKey(&apos;included&apos;, $data);
    $this-&gt;assertCount(2, $data[&apos;included&apos;]);
}
```

#### Testing JSON:API structure

```php
public function testGivenIncludedResourcesWhenRetrievingThenJsonApiStructureIsValid(): void
{
    // Arrange
    $customerTransfer = $this-&gt;tester-&gt;haveCustomer();
    $this-&gt;tester-&gt;haveAddress([&apos;customerReference&apos; =&gt; $customerTransfer-&gt;getCustomerReference()]);

    // Act
    $response = static::createClient()-&gt;request(
        &apos;GET&apos;,
        sprintf(&apos;/customers/%s?include=addresses&apos;, $customerTransfer-&gt;getCustomerReference())
    );

    // Assert
    $data = $response-&gt;toArray();

    // Verify main resource structure
    $this-&gt;assertArrayHasKey(&apos;data&apos;, $data);
    $this-&gt;assertArrayHasKey(&apos;type&apos;, $data[&apos;data&apos;]);
    $this-&gt;assertArrayHasKey(&apos;id&apos;, $data[&apos;data&apos;]);
    $this-&gt;assertArrayHasKey(&apos;attributes&apos;, $data[&apos;data&apos;]);
    $this-&gt;assertArrayHasKey(&apos;relationships&apos;, $data[&apos;data&apos;]);

    // Verify included resources structure
    foreach ($data[&apos;included&apos;] as $includedResource) {
        $this-&gt;assertArrayHasKey(&apos;type&apos;, $includedResource);
        $this-&gt;assertArrayHasKey(&apos;id&apos;, $includedResource);
        $this-&gt;assertArrayHasKey(&apos;attributes&apos;, $includedResource);
    }

    // Verify relationship linkage
    $relationshipData = $data[&apos;data&apos;][&apos;relationships&apos;][&apos;addresses&apos;][&apos;data&apos;];
    foreach ($relationshipData as $linkage) {
        $this-&gt;assertArrayHasKey(&apos;type&apos;, $linkage);
        $this-&gt;assertArrayHasKey(&apos;id&apos;, $linkage);
    }
}
```

## Writing Storefront API tests

### Basic test structure

Storefront API tests extend `StorefrontApiTestCase` and typically use mocks for read-only operations.

`tests/PyzTest/Glue/Customer/StorefrontApi/CustomersStorefrontApiTest.php`

```php
&lt;?php

namespace PyzTest\Glue\Customer\StorefrontApi;

use Codeception\Stub;
use Pyz\Client\Customer\CustomerClientInterface;
use PyzTest\Glue\Customer\StorefrontApiTester;
use SprykerTest\Shared\ApiPlatform\Test\StorefrontApiTestCase;

/**
 * @group PyzTest
 * @group Glue
 * @group Customer
 * @group StorefrontApi
 * @group CustomersStorefrontApiTest
 */
class CustomersStorefrontApiTest extends StorefrontApiTestCase
{
    protected StorefrontApiTester $tester;

    public function testGivenAuthenticatedCustomerWhenRetrievingProfileViaGetThenCustomerDataIsReturned(): void
    {
        // Arrange
        $customerClientStub = Stub::makeEmpty(CustomerClientInterface::class, [
            &apos;getCustomer&apos; =&gt; (new CustomerTransfer())
                -&gt;setEmail(&apos;customer@example.com&apos;)
                -&gt;setFirstName(&apos;John&apos;)
                -&gt;setLastName(&apos;Doe&apos;),
        ]);

        static::getContainer()-&gt;set(CustomerClientInterface::class, $customerClientStub);

        // Act
        static::createClient()-&gt;request(&apos;GET&apos;, &apos;/customers/me&apos;);

        // Assert
        $this-&gt;assertResponseIsSuccessful();
        $this-&gt;assertJsonContains([&apos;email&apos; =&gt; &apos;customer@example.com&apos;]);
    }
}
```

### Testing with service mocks

Register mocks with `setService()` rather than setting them on the container yourself — it is the supported seam, and it binds the mock whichever tier the test runs in:

```php
$this-&gt;tester-&gt;setService(CustomerClientInterface::class, $customerClientStub);
```

```php
public function testGivenMultipleCustomersWhenRetrievingCollectionViaGetThenAllCustomersAreReturned(): void
{
    // Arrange
    $customerClientStub = Stub::makeEmpty(CustomerClientInterface::class, [
        &apos;getCustomerCollection&apos; =&gt; [
            (new CustomerTransfer())-&gt;setEmail(&apos;customer1@example.com&apos;),
            (new CustomerTransfer())-&gt;setEmail(&apos;customer2@example.com&apos;),
        ],
    ]);

    static::getContainer()-&gt;set(CustomerClientInterface::class, $customerClientStub);

    // Act
    static::createClient()-&gt;request(&apos;GET&apos;, &apos;/customers&apos;);

    // Assert
    $this-&gt;assertResponseIsSuccessful();
    $this-&gt;assertJsonContains([&apos;@type&apos; =&gt; &apos;Collection&apos;]);
}
```

## Available assertions

### HTTP response assertions

```php
// Status codes
$this-&gt;assertResponseIsSuccessful();        // 2xx status code
$this-&gt;assertResponseStatusCodeSame(200);   // Exact status code
$this-&gt;assertResponseStatusCodeSame(201);   // Created
$this-&gt;assertResponseStatusCodeSame(204);   // No content
$this-&gt;assertResponseStatusCodeSame(400);   // Bad request
$this-&gt;assertResponseStatusCodeSame(401);   // Unauthorized
$this-&gt;assertResponseStatusCodeSame(403);   // Forbidden
$this-&gt;assertResponseStatusCodeSame(404);   // Not found
$this-&gt;assertResponseStatusCodeSame(422);   // Validation error

// Headers
$this-&gt;assertResponseHasHeader(&apos;Content-Type&apos;);
$this-&gt;assertResponseHeaderSame(&apos;Content-Type&apos;, &apos;application/ld+json; charset=utf-8&apos;);
$this-&gt;assertResponseHeaderNotSame(&apos;X-Custom-Header&apos;, &apos;value&apos;);

// Content
$this-&gt;assertResponseHasNoContent();        // Empty response body
```

### JSON assertions

```php
// Content matching
$this-&gt;assertJsonContains([&apos;email&apos; =&gt; &apos;test@example.com&apos;]);
$this-&gt;assertJsonContains([&apos;@type&apos; =&gt; &apos;Customer&apos;]);
$this-&gt;assertJsonContains([&apos;@type&apos; =&gt; &apos;Collection&apos;]);

// Array keys
$responseData = $response-&gt;toArray();
$this-&gt;assertArrayHasKey(&apos;customerReference&apos;, $responseData);
$this-&gt;assertArrayNotHasKey(&apos;password&apos;, $responseData);

// Validation violations
$this-&gt;assertJsonContains([&apos;@type&apos; =&gt; &apos;ConstraintViolationList&apos;]);
$this-&gt;assertJsonContains([
    &apos;violations&apos; =&gt; [
        [&apos;propertyPath&apos; =&gt; &apos;email&apos;],
    ],
]);

// Collection metadata
$this-&gt;assertJsonContains([&apos;totalItems&apos; =&gt; 10]);
$this-&gt;assertJsonContains([&apos;view&apos; =&gt; [&apos;@id&apos; =&gt; &apos;/customers?page=1&apos;]]);
```

### Custom API Platform assertions

```php
// JSON-LD context
$this-&gt;assertJsonContains([&apos;@context&apos; =&gt; &apos;/contexts/Customer&apos;]);

// Hydra collections
$this-&gt;assertJsonContains([&apos;hydra:totalItems&apos; =&gt; 5]);
$this-&gt;assertJsonContains([&apos;hydra:member&apos; =&gt; []]);

// IRI matching
$iri = $this-&gt;getIriFromResource($resource);
$this-&gt;assertMatchesRegularExpression(&apos;~^/customers/[A-Z0-9\-]+$~&apos;, $iri);
```

## Test data management

### Using Codeception helpers

Create test data using your project&apos;s tester helpers:

```php
// Create a customer
$customerTransfer = $this-&gt;tester-&gt;haveCustomer([
    &apos;email&apos; =&gt; &apos;test@example.com&apos;,
    &apos;firstName&apos; =&gt; &apos;John&apos;,
    &apos;lastName&apos; =&gt; &apos;Doe&apos;,
]);

// Create multiple customers
for ($i = 1; $i &lt;= 10; $i++) {
    $this-&gt;tester-&gt;haveCustomer([
        &apos;email&apos; =&gt; sprintf(&apos;customer%d@example.com&apos;, $i),
    ]);
}
```

### Cleanup strategies

#### Automatic cleanup (default)

The test kernel automatically cleans up after each test. No manual cleanup needed.

#### Manual cleanup (when needed)

```php
protected function tearDown(): void
{
    // Custom cleanup logic
    $this-&gt;tester-&gt;cleanupCustomers();

    parent::tearDown();
}
```

## Testing different media types

### JSON-LD (default)

```php
public function testJsonLdFormat(): void
{
    static::createClient()-&gt;request(&apos;GET&apos;, &apos;/customers&apos;, [
        &apos;headers&apos; =&gt; [
            &apos;Accept&apos; =&gt; &apos;application/ld+json&apos;,
        ],
    ]);

    $this-&gt;assertResponseHeaderSame(&apos;Content-Type&apos;, &apos;application/ld+json; charset=utf-8&apos;);
    $this-&gt;assertJsonContains([&apos;@context&apos; =&gt; &apos;/contexts/Customer&apos;]);
}
```

### JSON:API

```php
public function testJsonApiFormat(): void
{
    static::createClient()-&gt;request(&apos;GET&apos;, &apos;/customers&apos;, [
        &apos;headers&apos; =&gt; [
            &apos;Accept&apos; =&gt; &apos;application/vnd.api+json&apos;,
        ],
    ]);

    $this-&gt;assertResponseHeaderSame(&apos;Content-Type&apos;, &apos;application/vnd.api+json; charset=utf-8&apos;);
    $this-&gt;assertJsonContains([&apos;data&apos; =&gt; [&apos;type&apos; =&gt; &apos;Customer&apos;]]);
}
```

### HAL+JSON

```php
public function testHalJsonFormat(): void
{
    static::createClient()-&gt;request(&apos;GET&apos;, &apos;/customers&apos;, [
        &apos;headers&apos; =&gt; [
            &apos;Accept&apos; =&gt; &apos;application/hal+json&apos;,
        ],
    ]);

    $this-&gt;assertResponseHeaderSame(&apos;Content-Type&apos;, &apos;application/hal+json; charset=utf-8&apos;);
    $this-&gt;assertJsonContains([&apos;_links&apos; =&gt; [&apos;self&apos; =&gt; [&apos;href&apos; =&gt; &apos;/customers&apos;]]]);
}
```

## Advanced testing patterns

### Testing with filters

```php
public function testGivenFilterParamsWhenRetrievingCollectionThenFilteredResultsAreReturned(): void
{
    // Arrange
    $this-&gt;tester-&gt;haveCustomer([&apos;email&apos; =&gt; &apos;active@example.com&apos;, &apos;status&apos; =&gt; &apos;active&apos;]);
    $this-&gt;tester-&gt;haveCustomer([&apos;email&apos; =&gt; &apos;inactive@example.com&apos;, &apos;status&apos; =&gt; &apos;inactive&apos;]);

    // Act
    static::createClient()-&gt;request(&apos;GET&apos;, &apos;/customers?status=active&apos;);

    // Assert
    $this-&gt;assertResponseIsSuccessful();
    $responseData = static::createClient()-&gt;getResponse()-&gt;toArray();
    $this-&gt;assertCount(1, $responseData[&apos;hydra:member&apos;]);
}
```

### Testing sorting

```php
public function testGivenSortParamsWhenRetrievingCollectionThenSortedResultsAreReturned(): void
{
    // Arrange
    $this-&gt;tester-&gt;haveCustomer([&apos;lastName&apos; =&gt; &apos;Zulu&apos;]);
    $this-&gt;tester-&gt;haveCustomer([&apos;lastName&apos; =&gt; &apos;Alpha&apos;]);
    $this-&gt;tester-&gt;haveCustomer([&apos;lastName&apos; =&gt; &apos;Bravo&apos;]);

    // Act
    static::createClient()-&gt;request(&apos;GET&apos;, &apos;/customers?order[lastName]=asc&apos;);

    // Assert
    $this-&gt;assertResponseIsSuccessful();
    $responseData = static::createClient()-&gt;getResponse()-&gt;toArray();
    $members = $responseData[&apos;hydra:member&apos;];

    $this-&gt;assertEquals(&apos;Alpha&apos;, $members[0][&apos;lastName&apos;]);
    $this-&gt;assertEquals(&apos;Bravo&apos;, $members[1][&apos;lastName&apos;]);
    $this-&gt;assertEquals(&apos;Zulu&apos;, $members[2][&apos;lastName&apos;]);
}
```

### Testing error scenarios

```php
public function testGivenMalformedJsonWhenCreatingCustomerViaPostThenBadRequestIsReturned(): void
{
    // Act
    static::createClient()-&gt;request(&apos;POST&apos;, &apos;/customers&apos;, [
        &apos;body&apos; =&gt; &apos;{invalid-json}&apos;,
        &apos;headers&apos; =&gt; [
            &apos;Content-Type&apos; =&gt; &apos;application/json&apos;,
        ],
    ]);

    // Assert
    $this-&gt;assertResponseStatusCodeSame(400);
}

public function testGivenUnauthorizedRequestWhenAccessingProtectedResourceThen401IsReturned(): void
{
    // Act
    static::createClient()-&gt;request(&apos;GET&apos;, &apos;/customers/me&apos;);

    // Assert
    $this-&gt;assertResponseStatusCodeSame(401);
}
```

## Running tests

### Run all project tests (slow, not recommended)

```bash
docker/sdk cli vendor/bin/codecept run
```

### Run specific test suite

```bash
# Run Backend API tests only
docker/sdk cli vendor/bin/codecept run -c path/to/codeception.yml -g BackendApi

# Run Storefront API tests only
docker/sdk cli vendor/bin/codecept run -c path/to/codeception.yml  -g StorefrontApi
```

## Codeception configuration

### Suite configuration

Configure your test suite&apos;s `codeception.yml` to enable the necessary helpers:

`tests/PyzTest/Glue/Customer/BackendApi/codeception.yml`

```yaml
suite_namespace: PyzTest\Glue\Customer\BackendApi

actor: BackendApiTester

modules:
    enabled:
        - \SprykerTest\Shared\Testify\Helper\BootstrapHelper:
            applicationPluginProvider:
                class: Spryker\Glue\GlueBackendApiApplication\GlueBackendApiApplicationFactory
                method: getApplicationPlugins

paths:
    tests: .
    data: ../../../../../_data
    support: _support
    output: ../../../../../_output

settings:
    bootstrap: _bootstrap.php
    colors: true
    memory_limit: 1024M
```

**Key configuration points:**

- **BootstrapHelper**: Provides application plugins for the test kernel. This is optional and can be omitted if your tests do not require application-level dependencies.
- **suite_namespace**: Must match your test suite&apos;s PHP namespace
- **actor**: The tester class name (for example, `BackendApiTester`, `StorefrontApiTester`)

### ApiPlatformHelper modes

`ApiPlatformHelper` runs in one of two modes, selected in the suite&apos;s `codeception.yml`:

```yaml
modules:
    enabled:
        - \SprykerTest\ApiPlatform\Helper\ApiPlatformHelper:
            mode: &apos;project&apos;      # default; or &apos;core&apos; for module-level tests
```

| Mode | Use this when | Before suite | After suite |
|---|---|---|---|
| `project` (default) | Testing your own project&apos;s API resources end-to-end. | Validates that the project-generated resources in `src/Generated/Api/` exist. Skips generation. | Does nothing — the compiled container is preserved across runs for fast subsequent invocations. |
| `core` | Testing the `ApiPlatform` module itself (or any module that ships its own schemas in isolation from a project). | Generates fresh resources into `tests/_data/Api/{ApiType}/`. Requires `apiType` to be set on the helper. | Removes the generated resources and clears the compiled test kernel cache so the next suite starts from a clean slate. |

Use `project` mode for almost all real-world test suites — it is significantly faster because the compiled Symfony container is reused. Reach for `core` mode only when you intentionally want each suite to regenerate resources from scratch (typical when testing schema generation or a single module without a project around it).

When using `core` mode, declare which API type the suite exercises so the helper knows what to generate:

```yaml
modules:
    enabled:
        - \SprykerTest\ApiPlatform\Helper\ApiPlatformHelper:
            mode: &apos;core&apos;
            apiType: &apos;Storefront&apos;   # or &apos;Backend&apos;
```

### Helper classes

Create helper classes to manage test data:

`tests/PyzTest/Glue/Customer/Helper/CustomerHelper.php`

```php
&lt;?php

namespace PyzTest\Glue\Customer\Helper;

use Codeception\Module;
use Generated\Shared\Transfer\CustomerTransfer;
use Pyz\Zed\Customer\Business\CustomerFacadeInterface;

class CustomerHelper extends Module
{
    public function haveCustomer(array $seed = []): CustomerTransfer
    {
        $customerTransfer = (new CustomerTransfer())
            -&gt;fromArray($seed, true)
            -&gt;setEmail($seed[&apos;email&apos;] ?? sprintf(&apos;customer-%s@example.com&apos;, uniqid()))
            -&gt;setFirstName($seed[&apos;firstName&apos;] ?? &apos;Test&apos;)
            -&gt;setLastName($seed[&apos;lastName&apos;] ?? &apos;Customer&apos;);

        return $this-&gt;getCustomerFacade()-&gt;createCustomer($customerTransfer);
    }

    protected function getCustomerFacade(): CustomerFacadeInterface
    {
        return $this-&gt;getModule(&apos;\\PyzTest\\Shared\\Testify\\Helper\\Environment&apos;)
            -&gt;getFacade(&apos;Customer&apos;);
    }
}
```

## Best practices

### 1. Use descriptive test method names

```php
// ✅ Good
public function testGivenInvalidEmailWhenCreatingCustomerViaPostThenValidationErrorIsReturned(): void

// ❌ Bad
public function testCreate(): void
```

### 2. Follow Arrange-Act-Assert pattern

```php
public function testExample(): void
{
    // Arrange - Set up test data and preconditions
    $data = [&apos;email&apos; =&gt; &apos;test@example.com&apos;];

    // Act - Execute the operation being tested
    static::createClient()-&gt;request(&apos;POST&apos;, &apos;/customers&apos;, [&apos;json&apos; =&gt; $data]);

    // Assert - Verify the results
    $this-&gt;assertResponseIsSuccessful();
}
```

### 3. Test one thing per test

```php
// ✅ Good - Tests one specific validation rule
public function testGivenMissingEmailWhenCreatingCustomerThenValidationErrorIsReturned(): void
{
    static::createClient()-&gt;request(&apos;POST&apos;, &apos;/customers&apos;, [&apos;json&apos; =&gt; []]);
    $this-&gt;assertJsonContains([&apos;violations&apos; =&gt; [[&apos;propertyPath&apos; =&gt; &apos;email&apos;]]]);
}

// ❌ Bad - Tests multiple unrelated things
public function testCustomerCreation(): void
{
    // Tests validation, creation, retrieval, update all in one test
}
```

### 4. Use meaningful test data

```php
// ✅ Good
$customerData = [
    &apos;email&apos; =&gt; &apos;john.doe@example.com&apos;,  // Realistic email
    &apos;firstName&apos; =&gt; &apos;John&apos;,               // Realistic name
    &apos;lastName&apos; =&gt; &apos;Doe&apos;,
];

// ❌ Bad
$customerData = [
    &apos;email&apos; =&gt; &apos;a@b.c&apos;,    // Not realistic
    &apos;firstName&apos; =&gt; &apos;x&apos;,     // Not meaningful
    &apos;lastName&apos; =&gt; &apos;y&apos;,
];
```

### 5. Clean up test data appropriately

```php
// For Backend API tests - use tester helpers for setup
$customer = $this-&gt;tester-&gt;haveCustomer([&apos;email&apos; =&gt; &apos;test@example.com&apos;]);

// Cleanup happens automatically via test kernel shutdown
```

### 6. Test error cases

```php
// Always test both success and failure scenarios
public function testSuccessfulCreation(): void { /* ... */ }
public function testValidationErrors(): void { /* ... */ }
public function testDuplicateEmail(): void { /* ... */ }
public function testNotFound(): void { /* ... */ }
```

### 7. Use constants for repeated values

```php
class CustomersBackendApiTest extends BackendApiTestCase
{
    private const TEST_EMAIL = &apos;test@example.com&apos;;
    private const TEST_FIRST_NAME = &apos;John&apos;;

    public function testExample(): void
    {
        $data = [
            &apos;email&apos; =&gt; self::TEST_EMAIL,
            &apos;firstName&apos; =&gt; self::TEST_FIRST_NAME,
        ];
        // ...
    }
}
```

### 8. Group related tests

```php
/**
 * @group PyzTest
 * @group Glue
 * @group Customer
 * @group BackendApi
 * @group CustomersBackendApiTest
 * @group ValidationTests
 */
class CustomersBackendApiTest extends BackendApiTestCase
{
    // Run only validation tests:
    // vendor/bin/codecept run -g ValidationTests
}
```

## Troubleshooting

### Generated resources not found

**Problem:** Test fails with &quot;Class not found&quot; for generated resource.

**Solution:**

1. Verify autoload configuration in `composer.json`:

```json
{
    &quot;autoload-dev&quot;: {
        &quot;psr-4&quot;: {
            &quot;PyzTest\\&quot;: &quot;tests/PyzTest/&quot;,
            &quot;Generated\\TestApi\\&quot;: &quot;tests/_data/Api/&quot;
        }
    }
}
```

2. Run composer dump-autoload:

```bash
docker/sdk cli composer dump-autoload
```

### Test kernel boot failures

**Problem:** Tests fail with kernel boot errors.

**Solution:**

Ensure your test case extends the correct base class:

```php
// For Backend API
use PyzTest\Shared\ApiPlatform\Test\BackendApiTestCase;

class CustomersBackendApiTest extends BackendApiTestCase
{
    // ...
}

// For Storefront API
use PyzTest\Shared\ApiPlatform\Test\StorefrontApiTestCase;

class CustomersStorefrontApiTest extends StorefrontApiTestCase
{
    // ...
}
```

### Assertion failures with JSON-LD

**Problem:** JSON assertions fail with `@context` or `@type` fields.

**Solution:**

Use JSON-LD specific assertions:

```php
// ✅ Correct
$this-&gt;assertJsonContains([&apos;@type&apos; =&gt; &apos;Customer&apos;]);
$this-&gt;assertJsonContains([&apos;@context&apos; =&gt; &apos;/contexts/Customer&apos;]);

// ❌ Wrong
$this-&gt;assertJsonContains([&apos;type&apos; =&gt; &apos;Customer&apos;]);
```

### Tester helper not found

**Problem:** `$this-&gt;tester` property shows as undefined.

**Solution:**

1. Verify your tester class exists in the correct location
2. Check that the tester is properly type-hinted in your test:

```php
class CustomersBackendApiTest extends BackendApiTestCase
{
    protected BackendApiTester $tester;  // Must be declared
}
```

3. Rebuild Codeception actors:

```bash
docker/sdk cli vendor/bin/codecept build
```

## Next steps

- [API Platform Enablement](/docs/dg/dev/architecture/api-platform/enablement.html) - Creating API resources
- [Resource Schemas](/docs/dg/dev/architecture/api-platform/resource-schemas.html) - Resource schema reference
- [Validation Schemas](/docs/dg/dev/architecture/api-platform/validation-schemas.html) - Validation schema reference
- [Troubleshooting](/docs/dg/dev/architecture/api-platform/troubleshooting.html) - Common issues and solutions
- [Codeception Documentation](https://codeception.com/docs/Introduction) - Codeception framework docs
- [API Platform Testing](https://api-platform.com/docs/symfony/testing/) - Official API Platform testing guide
</description>
            <pubDate>Thu, 06 Aug 2026 11:27:02 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/architecture/api-platform/testing.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/architecture/api-platform/testing.html</guid>
            
            
        </item>
        
        <item>
            <title>Optimizing Jenkins execution with the resource-aware queue worker</title>
            <description>Spryker ships a **resource-aware queue worker** (`ResourceAwareQueueWorker`) that replaces the default queue worker with a production-grade implementation focused on system stability and efficient resource utilization. Starting with `spryker/queue` 1.29.0, it is the recommended default queue worker, and it is enabled by default in the Spryker demo shops.

This document explains the problem it solves, how to enable and configure it, how it works internally, and how to back-port the concept to older Spryker versions.

{% info_block infoBox &quot;Enabled by default in the demo shops&quot; %}

The resource-aware queue worker is enabled by default in the Spryker B2B Marketplace demo shop, where `Pyz\Zed\Queue\QueueConfig::isResourceAwareQueueWorkerEnabled()` returns `true`. The demo shop enables the worker on top of the following package versions:

- `spryker/queue` 1.29.0
- `spryker/console` 4.19.0
- `spryker/symfony-messenger` 1.8.1

{% endinfo_block %}

## Problem

The default Spryker system requires a `queue:worker:start` command to be continuously running for each store to process queues. In multi-store setups, this creates several challenges:

- **Jenkins executor exhaustion**: By default, Jenkins has two executors. With multiple stores, workers compete for executor slots, causing delays in content publishing.
- **Unpredictable memory consumption**: Multiple workers processing heavy messages simultaneously can spike memory usage, causing crashes or out-of-memory (OOM) conditions.
- **No resource awareness**: The default worker spawns child processes based on message presence, without considering available system resources.
- **Per-store overhead**: Even stores with empty queues occupy an executor slot for scanning.

## Solution overview

The resource-aware queue worker replaces per-store workers with a single worker that manages a fixed-size process pool and monitors system resources before spawning child processes.

![Resource-aware queue worker diagram](https://spryker.s3.eu-central-1.amazonaws.com/docs/scos/dev/tutorials-and-howtos/howtos/howto-reduce-jenkins-execution-cost-without-refactoring/OneWorker-diagram.png)

Key features:

- **Process pool**: A fixed-size array (default 5) of concurrent processes shared across all queues and stores, providing predictable resource consumption.
- **Memory monitoring**: Checks available system memory before spawning each child process, preventing OOM conditions.
- **Memory leak detection**: Tracks its own memory growth and exits gracefully when a leak is detected, allowing Jenkins to restart it cleanly.
- **Dynamic queue prioritization**: Intelligent strategy that prioritizes queues based on message volume and configurable modes (publish-first, sync-first, biggest-first).
- **Comprehensive statistics**: Tracks cycles, process counts, error rates, and queue-level metrics for operational visibility.
- **Graceful shutdown**: Handles Unix signals for clean termination and waits for child processes to complete.

## Enabling the resource-aware queue worker

### Prerequisites

- `spryker/queue` &gt;= 1.22.0
- `spryker/rabbit-mq` &gt;= 2.21.1
- `spryker/queue-extension` &gt;= 1.1.0
- RabbitMQ as the queue adapter. To verify this, check the following two configuration points in your project:
  1. In `config/Shared/config_default.php`, the default queue adapter must be set to `RabbitMqAdapter`:

     ```php
     use Spryker\Client\RabbitMq\Model\RabbitMqAdapter;
     use Spryker\Shared\Queue\QueueConfig;
     use Spryker\Shared\Queue\QueueConstants;

     $config[QueueConstants::QUEUE_ADAPTER_CONFIGURATION_DEFAULT] = [
         QueueConfig::CONFIG_QUEUE_ADAPTER =&gt; RabbitMqAdapter::class,
         QueueConfig::CONFIG_MAX_WORKER_NUMBER =&gt; 1,
     ];
     ```

  2. In `src/Pyz/Client/Queue/QueueDependencyProvider.php`, the `createQueueAdapters()` method must return the RabbitMQ adapter:

     ```php
     protected function createQueueAdapters(Container $container): array
     {
         return [
             $container-&gt;getLocator()-&gt;rabbitMq()-&gt;client()-&gt;createQueueAdapter(),
         ];
     }
     ```

To verify the installed versions:

```bash
composer show spryker/queue spryker/rabbit-mq spryker/queue-extension
```

### Step 1: Register the metrics plugin

Register the `RabbitMqQueueMetricsReaderPlugin` in your `QueueDependencyProvider` to supply queue metrics (message counts, batch sizes) to the worker:

**src/Pyz/Zed/Queue/QueueDependencyProvider.php**

```php

use Spryker\Zed\RabbitMq\Communication\Plugin\Queue\RabbitMqQueueMetricsReaderPlugin;

class QueueDependencyProvider extends SprykerQueueDependencyProvider
{
    /**
     * @return array&lt;\Spryker\Zed\QueueExtension\Dependency\Plugin\QueueMetricsReaderPluginInterface&gt;
     */
    protected function getQueueMetricsReaderPlugins(): array
    {
        return [
            new RabbitMqQueueMetricsReaderPlugin(),
        ];
    }
}
```

Also in `QueueDependencyProvider`, replace `EventRetryQueueMessageProcessorPlugin` with `EventQueueMessageProcessorPlugin` for the event retry queue. This is recommended since `spryker/event:^2.17.1`.

Both plugins define how the event retry queue behaves when a message fails. `EventQueueMessageProcessorPlugin` processes failed messages directly in the retry queue, leaving the main queue unaffected. `EventRetryQueueMessageProcessorPlugin` routes failed messages back to the main queue (without the `.retry` postfix), which can slow down new message processing under high load:

```php
use Spryker\Zed\Event\Communication\Plugin\Queue\EventQueueMessageProcessorPlugin;
use Spryker\Shared\Event\EventConstants;
// ...

protected function getProcessorMessagePlugins(Container $container): array
{
    return [
        EventConstants::EVENT_QUEUE_RETRY =&gt; new EventQueueMessageProcessorPlugin(),
    ];
}
```

### Step 2: Enable via configuration

Add the following to `config/Shared/config_default.php`:

```php
use Spryker\Shared\Queue\QueueConstants;

// Enable the resource-aware worker
$config[QueueConstants::RESOURCE_AWARE_QUEUE_WORKER_ENABLED] = (bool)getenv(&apos;RESOURCE_AWARE_QUEUE_WORKER_ENABLED&apos;) ?: true;
```

You can also enable it per environment using the `RESOURCE_AWARE_QUEUE_WORKER_ENABLED` environment variable.

Alternatively, you can enable the worker by overriding `isResourceAwareQueueWorkerEnabled()` in the project-level `Pyz\Zed\Queue\QueueConfig` to return `true`. This is the approach used in the Spryker demo shops:

```php
&lt;?php

namespace Pyz\Zed\Queue;

use Spryker\Zed\Queue\QueueConfig as SprykerQueueConfig;

class QueueConfig extends SprykerQueueConfig
{
    public function isResourceAwareQueueWorkerEnabled(): bool
    {
        return true;
    }
}
```

### Step 3: Configure a single Jenkins job

Replace per-store `queue:worker:start` jobs with a single job:

```php
// config/Zed/cronjobs/jenkins.php
$jobs[] = [
    &apos;name&apos; =&gt; &apos;queue-worker&apos;,
    &apos;command&apos; =&gt; &apos;$PHP_BIN vendor/bin/console queue:worker:start&apos;,
    &apos;schedule&apos; =&gt; &apos;* * * * *&apos;,
    &apos;enable&apos; =&gt; true,
    &apos;stores&apos; =&gt; [&apos;DE&apos;], // Use any one store/region as the entry point
];
```

The resource-aware worker automatically processes queues for all stores and regions.

## Configuration reference

All configuration constants are defined in `Spryker\Shared\Queue\QueueConstants`. They can be set in `config/Shared/config_default.php` and overridden with environment variables where supported.

### Core settings

| Constant                              | Environment variable                  | Type                   | Default | Description                                                                                                                       |
|:--------------------------------------|:--------------------------------------|:-----------------------|:--------|:----------------------------------------------------------------------------------------------------------------------------------|
| `RESOURCE_AWARE_QUEUE_WORKER_ENABLED` | `RESOURCE_AWARE_QUEUE_WORKER_ENABLED` | boolean                | `false` | Enables the resource-aware worker. When turned off, the default worker is used.                                                   |
| `QUEUE_WORKER_WAIT_LIMIT_ENABLED`     | -                                     | boolean                | `false` | When enabled, stops the queue worker once execution time exceeds `QUEUE_WORKER_MAX_THRESHOLD_SECONDS`. Use `QUEUE_WORKER_MAX_WAITING_SECONDS` (default: 30 seconds) to control how long the system waits for running subprocesses to complete after the threshold is reached. Prevents a single stuck subprocess from blocking the entire worker. |
| `QUEUE_WORKER_MAX_THRESHOLD_SECONDS`  | `QUEUE_WORKER_MAX_THRESHOLD_SECONDS`  | integer (seconds)      | `59`    | Maximum runtime per worker invocation. Set to slightly under one minute so Jenkins can restart the worker on the next cron cycle. |
| `QUEUE_WORKER_INTERVAL_MILLISECONDS`  | `QUEUE_WORKER_INTERVAL_MILLISECONDS`  | integer (milliseconds) | `1000`  | Minimum delay between spawning consecutive child processes. Lower values (100-500) increase throughput at the cost of CPU.        |

### Process pool settings

| Constant                                                      | Environment variable         | Type                   | Default | Description                                                                             |
|:--------------------------------------------------------------|:-----------------------------|:-----------------------|:--------|:----------------------------------------------------------------------------------------|
| `QUEUE_WORKER_MAX_PROCESSES`                                  | `QUEUE_WORKER_MAX_PROCESSES` | integer                | `5`     | Maximum number of concurrent child processes across all queues and stores. Range: 5-10. |
| `QUEUE_WORKER_PROCESSES_COMPLETE_TIMEOUT`                     | -                            | integer (seconds)      | `300`   | Maximum time to wait for child processes to complete after the main loop ends.          |
| `QUEUE_WORKER_CHECK_PROCESSES_COMPLETE_INTERVAL_MILLISECONDS` | -                            | integer (milliseconds) | `1000`  | Interval for checking whether child processes have completed during the shutdown wait.  |

### Memory management settings

| Constant                                   | Environment variable                       | Type                 | Default | Description                                                                                                                                                                                                    |
|:-------------------------------------------|:-------------------------------------------|:---------------------|:--------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `QUEUE_WORKER_FREE_MEMORY_BUFFER`          | `QUEUE_WORKER_FREE_MEMORY_BUFFER`          | integer (megabytes)  | `750`   | Minimum free system memory required before spawning a new child process.                                                                                                                                       |
| `QUEUE_WORKER_MEMORY_READ_PROCESS_TIMEOUT` | `QUEUE_WORKER_MEMORY_READ_PROCESS_TIMEOUT` | integer (seconds)    | `5`     | Timeout for reading system memory information from `/proc/meminfo`.                                                                                                                                            |
| `QUEUE_WORKER_IGNORE_MEMORY_READ_FAILURE`  | -                                          | boolean              | `false` | When `true`, treats unreadable memory as &quot;enough memory&quot; instead of throwing an exception.                                                                                                                     |
| `QUEUE_WORKER_MEMORY_MAX_GROWTH_FACTOR`    | -                                          | integer (percentage) | `50`    | Maximum allowed worker memory growth before the worker exits to prevent memory leaks. For example, `50` means the worker exits if its own memory consumption grows by more than 50% from the initial baseline. |

### Queue prioritization settings

| Constant                                              | Type              | Default | Description                                                                                                                               |
|:------------------------------------------------------|:------------------|:--------|:------------------------------------------------------------------------------------------------------------------------------------------|
| `QUEUE_PROCESSING_WORKER_DYNAMIC_MODE`                | integer (bitmask) | `0`     | Controls queue prioritization modes. Modes can be combined using bitwise OR. See [Queue processing strategy](#queue-processing-strategy). |
| `QUEUE_PROCESSING_BIG_QUEUE_THRESHOLD_BATCHES_AMOUNT` | integer           | `100`   | Number of batches that defines the threshold for a queue to be considered &quot;big&quot; for prioritization purposes.                              |
| `QUEUE_PROCESSING_LIMIT_OF_PROCESSES_PER_QUEUE`       | integer           | `10`    | Maximum number of concurrent processes per individual queue.                                                                              |

### Example configuration

**config/Shared/config_default.php**

```php
use Spryker\Shared\Queue\QueueConstants;

// Enable the resource-aware worker
$config[QueueConstants::RESOURCE_AWARE_QUEUE_WORKER_ENABLED] = (bool)getenv(&apos;RESOURCE_AWARE_QUEUE_WORKER_ENABLED&apos;) ?: true;

// Stop worker when execution time is exceeded; wait up to QUEUE_WORKER_MAX_WAITING_SECONDS for subprocesses
$config[QueueConstants::QUEUE_WORKER_WAIT_LIMIT_ENABLED] = true;

// Process pool
$config[QueueConstants::QUEUE_WORKER_MAX_PROCESSES] = 10;

// Memory management
$config[QueueConstants::QUEUE_WORKER_FREE_MEMORY_BUFFER] = (int)getenv(&apos;QUEUE_WORKER_FREE_MEMORY_BUFFER&apos;) ?: 750;
$config[QueueConstants::QUEUE_WORKER_MEMORY_READ_PROCESS_TIMEOUT] = (int)getenv(&apos;QUEUE_WORKER_MEMORY_READ_PROCESS_TIMEOUT&apos;) ?: 5;

// Timing
$config[QueueConstants::QUEUE_WORKER_MAX_THRESHOLD_SECONDS] = 59;
$config[QueueConstants::QUEUE_WORKER_INTERVAL_MILLISECONDS] = 1000;

// Process completion
$config[QueueConstants::QUEUE_WORKER_PROCESSES_COMPLETE_TIMEOUT] = 600;
```

## How it works

### Main execution loop

When `queue:worker:start` is executed with the resource-aware worker enabled, the following flow runs for the configured threshold duration (default 59 seconds):

![Resource-aware worker flow](https://spryker.s3.eu-central-1.amazonaws.com/docs/scos/dev/tutorials-and-howtos/howtos/howto-reduce-jenkins-execution-cost-without-refactoring/NewWorker+Flow.png)

1. **Cycle start**: The worker increments cycle counter.
2. **Process scan**: Iterates through the process pool to detect completed processes, free slots, and log errors from failed processes.
3. **Memory check**: Reads free system memory from `/proc/meminfo`. If free memory is below the configured buffer, the cycle is skipped.
4. **Slot check**: If no free process slot is available, the cycle is skipped.
5. **Cooldown check**: If the minimum delay between spawns has not elapsed, the cycle is skipped.
6. **Process spawn**: The queue processing strategy selects the next queue. A child process (`queue:task:start &lt;queue-name&gt;`) is spawned and placed into the free slot.
7. **Memory leak check**: The worker compares its own current memory usage against the initial baseline. If growth exceeds the configured threshold, the worker exits gracefully.
8. **Shutdown**: After the main loop, the worker waits for all remaining child processes to complete (up to the configured timeout), then logs statistics.

### Process pool

The process pool is a fixed-size array (`SplFixedArray`) where each slot holds a reference to a running child process. This design provides:

- **Predictable concurrency**: At most N processes run simultaneously, regardless of the number of stores or queues.
- **Predictable memory consumption**: Since the pool size is fixed, you can estimate maximum memory usage as `pool_size * max_memory_per_task + worker_overhead`.
- **Efficient slot reuse**: Completed processes free their slots immediately for new work.

### System resource monitoring

The `SystemResourcesManager` provides two key capabilities:

**Free memory detection** reads `/proc/meminfo` (with a fallback to `cat /proc/meminfo` via subprocess) and returns the maximum of `MemFree` and `MemAvailable` in megabytes. Before every process spawn, the worker checks whether free memory exceeds the configured buffer.

**Worker memory growth tracking** captures the initial `memory_get_peak_usage()` on first invocation and calculates the percentage growth on each subsequent check. If growth exceeds the configured factor (default 50%), the worker exits gracefully. Since Jenkins restarts it on the next cron cycle, this effectively prevents indefinite memory leaks.

### Queue processing strategy

The `DynamicOrderQueueProcessingStrategy` combines multiple prioritization modes to determine which queue to process next. It scans all queues for all stores, calculates a priority score for each, and returns them in priority order.

**Available modes** (combinable via bitwise OR):

| Mode                 | Value | Behavior                                                         |
|:---------------------|:------|:-----------------------------------------------------------------|
| Default order        | `0`   | Processes queues in definition order                             |
| Prefer publish       | `2`   | Prioritizes publish queues                                       |
| Prefer sync          | `4`   | Prioritizes sync queues                                          |
| Prefer big           | `8`   | Prioritizes queues with more than the configured batch threshold |
| Prefer small         | `16`  | Prioritizes queues below the batch threshold                     |
| Prefer default store | `32`  | Prioritizes the default store&apos;s queues                           |
| Prefer fast          | `64`  | Prioritizes fast-processing queues                               |
| Prefer slow          | `128` | Prioritizes slow-processing queues                               |
| Only preferred       | `256` | Restricts processing to only preferred queues                    |

**Queues with fewer messages than one batch size receive the lowest priority** to avoid wasting process slots on near-empty queues.

**Per-queue process limits** prevent any single queue from monopolizing the entire pool. The `QUEUE_PROCESSING_LIMIT_OF_PROCESSES_PER_QUEUE` constant caps how many concurrent processes target the same queue.

The strategy also supports **runtime dynamic settings updates** through `DynamicSettingsUpdaterPluginInterface` plugins, allowing modes and thresholds to be adjusted during execution based on external signals.

## Worker statistics and logs

The resource-aware worker collects comprehensive statistics during each invocation and prints a summary at the end.

![Worker statistics log](https://spryker.s3.eu-central-1.amazonaws.com/docs/scos/dev/tutorials-and-howtos/howtos/howto-reduce-jenkins-execution-cost-without-refactoring/stats-log.png)

**Cycle metrics**:

- Total cycles executed
- Skip cycles (throttled due to resource constraints)
- Empty cycles (no messages in any queue)
- No-slot cycles (all process pool slots are busy)
- No-memory cycles (insufficient free system memory)
- Cooldown cycles (minimum spawn interval not yet elapsed)

**Process metrics**:

- Total processes spawned
- Failed processes (non-zero exit code)
- Maximum concurrent processes observed

**Queue metrics**:

- Per-queue task counts
- Per-store or per-region task counts
- Error distribution by exit code

**Success rate** is calculated as `(spawned - failed) / spawned * 100%`.

![Worker statistics summary](https://spryker.s3.eu-central-1.amazonaws.com/docs/scos/dev/tutorials-and-howtos/howtos/howto-reduce-jenkins-execution-cost-without-refactoring/stats-summary.png)

### Error logging

Output from failed child processes is captured in the main worker&apos;s standard output, including the command line, standard output, and error output. This simplifies troubleshooting by providing all relevant information in one log stream.

![Error logging](https://spryker.s3.eu-central-1.amazonaws.com/docs/scos/dev/tutorials-and-howtos/howtos/howto-reduce-jenkins-execution-cost-without-refactoring/stats-error-log.png)

Use the `-vvv` flag when running `queue:worker:start` to see detailed debug-level output, including per-cycle memory and timing information.

## Tuning recommendations

### Process pool size

Start with the default of 5 and adjust based on observation:

- **Increase** if statistics show frequent no-slot cycles and your system has available memory and CPU.
- **Decrease** if you observe memory pressure, OOM conditions, or high CPU contention.
- A good starting point is 1-2 processes per available CPU core.

### Free memory buffer

The default 750 MB works for most environments. Adjust based on your host&apos;s total RAM and per-task memory consumption:

- **Small hosts (4 GB RAM)**: Use 512-750 MB.
- **Large hosts (8+ GB RAM)**: You can increase the pool size while keeping the buffer at 750 MB.
- The buffer must accommodate: the memory needed by the next spawned process, plus headroom for memory spikes in already running processes.

### Spawn interval

The default 1000 ms (1 second) is conservative. For latency-sensitive environments:

- Lower to 100-500 ms for faster throughput.
- Higher values reduce CPU overhead from the worker&apos;s main loop.

### Monitoring

Use the worker statistics to identify bottlenecks:

- **High no-memory cycles**: Reduce pool size or increase instance memory.
- **High no-slot cycles**: Increase pool size if resources allow.
- **High empty cycles**: Queues are mostly idle; the worker is working as expected.
- **Failed processes**: Investigate the error output for root causes (memory limits, database connection issues).

{% info_block warningBox &quot;Performance monitoring&quot; %}

Instance performance also depends on other jobs running on Jenkins, such as data import and custom plugins. These can affect the overall performance and runtime of your Publish and Synchronize processes. Always analyze them with [Application Performance Monitoring](/docs/dg/dev/integrate-and-configure/configure-services.html#new-relic) or [local profiling](/docs/scos/dev/tutorials-and-howtos/howtos/howto-setup-xdebug-profiling.html).

{% endinfo_block %}

## Backporting to older Spryker versions

If you are on a Spryker version prior to `202512.0` and cannot upgrade, you can implement the resource-aware worker concept at the project level. This section provides a high-level guide for the approach.

{% info_block warningBox &quot;Unsupported customization&quot; %}

The following is a project-level customization and is not officially supported by Spryker. The built-in `ResourceAwareQueueWorker` in `202512.0` supersedes this approach. Upgrade when possible.

{% endinfo_block %}

### Required components

1. **Custom Worker class**: Implement `WorkerInterface` with a fixed-size process pool (`SplFixedArray`), a main loop bounded by a time threshold, and free memory checks before each process spawn. The worker should iterate through its pool to detect completed processes and reuse freed slots.

2. **System resources manager**: Create a class that reads `/proc/meminfo` to determine free system memory and tracks the worker&apos;s own memory growth using `memory_get_peak_usage(true)`.

3. **Queue scanner**: Build a component that queries RabbitMQ for message counts per queue per store. Implement a cooldown period (for example, 5 seconds) to avoid repeatedly scanning empty queues.

4. **Queue processing strategy**: Implement a strategy interface with a `getNextQueue()` method. A simple ordered strategy iterates through queues; a more advanced strategy can prioritize based on message volume.

5. **Process manager extension**: Extend the Spryker `ProcessManager` to prefix queue names with store codes, enabling a single worker to distinguish processes across stores.

6. **RabbitMQ metrics exposure**: Expose the RabbitMQ `queue_declare` passive method to the business layer, allowing the scanner to read queue statistics without modifying queue state.

### Integration

Wire the components through a custom `QueueBusinessFactory`:

- Override `createWorker()` to return your custom worker based on a config flag.
- Register the custom factory in the project-level `QueueDependencyProvider`.
- Configure a single Jenkins job instead of per-store jobs.

### Configuration

Define project-level constants for pool size, memory buffer, memory read timeout, and memory growth threshold, mirroring the constants described in [Configuration reference](#configuration-reference).

## Stable workers

For Spryker PaaS environments, **Stable Workers** provide an alternative approach to background job optimization. Both the resource-aware queue worker and Stable Workers address the same core problems (memory management, process isolation, and stability), but they differ in scope:

- **Resource-aware queue worker**: Runs within Jenkins or any scheduler. Manages its own process pool and memory monitoring. Suitable for self-hosted, isolated, or non-Jenkins setups.
- **Stable Workers**: A PaaS-managed service using Amazon ECS with configurable capacity providers and Auto Scaling Groups. Provides infrastructure-level isolation and scaling.

Both solutions can coexist. Stable Workers handle P&amp;S workloads while the resource-aware queue worker can manage other queue-based jobs on Jenkins.

For more details, see [Stable Workers](/docs/dg/dev/backend-development/cronjobs/stable-workers.html).
</description>
            <pubDate>Thu, 06 Aug 2026 10:54:35 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/backend-development/cronjobs/optimizing-jenkins-execution.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/backend-development/cronjobs/optimizing-jenkins-execution.html</guid>
            
            
        </item>
        
        <item>
            <title>Configure direct synchronize</title>
            <description>&lt;p&gt;To optimize performance and flexibility, you can enable direct synchronization on the project level. This approach uses in-memory storage to retain all synchronization events instead of sending them to the queue. With this setup, you can control if entities are synchronized directly or through the traditional queue-based method.&lt;/p&gt;
&lt;p&gt;For more details on direct sync, see &lt;a href=&quot;/docs/dg/dev/backend-development/data-manipulation/data-publishing/publish-and-synchronization#synchronization-types&quot;&gt;Synchronization types&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To enable direct synchronization, do the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add &lt;code&gt;DirectSynchronizationConsolePlugin&lt;/code&gt; to &lt;code&gt;ConsoleDependencyProvider::getEventSubscriber()&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enable the &lt;code&gt;SynchronizationBehaviorConfig::isDirectSynchronizationEnabled()&lt;/code&gt; configuration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Rebuild Propel models - &lt;code&gt;vendor/bin/console propel:install&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;src/Pyz/Zed/Console/ConsoleDependencyProvider.php&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Pyz\Zed\Console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\Console\ConsoleDependencyProvider&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SprykerConsoleDependencyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\Kernel\Container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\Synchronization\Communication\Plugin\Console\DirectSynchronizationConsolePlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ConsoleDependencyProvider&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SprykerConsoleDependencyProvider&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cd&quot;&gt;/**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return array&amp;lt;\Symfony\Component\EventDispatcher\EventSubscriberInterface&amp;gt;
     */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getEventSubscriber&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Container&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;array&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DirectSynchronizationConsolePlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;src/Pyz/Zed/Console/ConsoleDependencyProvider.php&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Pyz\Zed\SynchronizationBehavior&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\SynchronizationBehavior\SynchronizationBehaviorConfig&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SprykerSynchronizationBehaviorConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SynchronizationBehaviorConfig&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SprykerSynchronizationBehaviorConfig&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isDirectSynchronizationEnabled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This configuration enables direct sync for all entities with synchronization behavior.&lt;/p&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Recommended: Enable &lt;code&gt;QueueConfig::isReducedSyncQueueScanEnabled()&lt;/code&gt; to reduce how often the queue worker scans the sync queue. Because direct synchronization writes events to in-memory storage instead of the sync queue, the sync queue stays mostly empty, so the worker does not need to scan it as frequently. This method requires the following package versions:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;spryker/queue&lt;/code&gt; &amp;gt;= 1.29.0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spryker/console&lt;/code&gt; &amp;gt;= 4.19.0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spryker/symfony-messenger&lt;/code&gt; &amp;gt;= 1.8.1&lt;/li&gt;
&lt;/ul&gt;
&lt;section class=&apos;info-block info-block--warning&apos;&gt;&lt;i class=&apos;info-block__icon icon-warning&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;&lt;div class=&quot;info-block__title&quot;&gt;Enable only with direct sync&lt;/div&gt;
&lt;p&gt;Enable &lt;code&gt;isReducedSyncQueueScanEnabled()&lt;/code&gt; only when direct synchronization is enabled. With the traditional queue-based synchronization, the sync queue still receives events and must be scanned at the regular interval.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;p&gt;&lt;strong&gt;src/Pyz/Zed/Queue/QueueConfig.php&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Pyz\Zed\Queue&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\Queue\QueueConfig&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SprykerQueueConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;QueueConfig&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SprykerQueueConfig&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isReducedSyncQueueScanEnabled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Optional: To disable direct sync for specific entities, add an additional parameter in the Propel schema:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;table&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;spy_table_storage&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;identifierQuoting=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;behavior&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;synchronization&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;parameter&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;direct_sync_disabled&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/behavior&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;environment-limitations-related-to-dynamic-multi-store&quot;&gt;Environment limitations related to Dynamic Multi-Store&lt;/h2&gt;
&lt;p&gt;When Dynamic Multi-Store (DMS) is enabled, there’re no environment limitations for direct sync.&lt;/p&gt;
&lt;p&gt;When DMS is disabled, direct sync has the following limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Single-store configuration: The feature is only supported for configurations with a single store.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Multi-store configuration with namespace consistency: For configurations with multiple stores, all stores must use the same Storage and Search namespaces.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example configuration for multiple stores:&lt;/p&gt;
&lt;div class=&quot;language-yml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;stores&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;DE&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;broker&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;de-docker&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;key_value_store&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;search&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;AT&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;broker&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;at-docker&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;key_value_store&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;search&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;search&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
            <pubDate>Thu, 06 Aug 2026 10:54:35 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/backend-development/data-manipulation/data-publishing/configurartion/configure-direct-synchronize.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/backend-development/data-manipulation/data-publishing/configurartion/configure-direct-synchronize.html</guid>
            
            
        </item>
        
        <item>
            <title>Security release notes 202608.0</title>
            <description>This document describes the security-related issues that have been recently resolved.

For additional support with this content, [contact our support](https://support.spryker.com/). If you found a new security vulnerability, contact us at [security@spryker.com](mailto:security@spryker.com).

## Removal of eval() function

Use of the eval() function has been removed from the codebase. Even though no security issues were identified due to its use, it was removed in order to follow security best practices.

### Affected modules

- `spryker/testify`: &lt; 3.66.0

### Fix the vulnerability

Update the affected Spryker package:

```bash
composer update spryker/testify:&quot;^3.66.0&quot;
composer show spryker/testify # Verify the version
```

Add or adjust the $config[TestifyConstants::IS_DATA_BUILDER_RULE_EVAL_ENABLED] line within the `config/Shared/config_default.php` file:

```bash
use Spryker\Shared\Testify\TestifyConstants;

if (class_exists(TestifyConstants::class)) {
    $config[TestifyConstants::IS_DATA_BUILDER_RULE_EVAL_ENABLED] = false;
}
```</description>
            <pubDate>Thu, 06 Aug 2026 10:48:15 +0000</pubDate>
            <link>https://docs.spryker.com/docs/about/all/releases/security-releases/security-release-notes-202608.0.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/about/all/releases/security-releases/security-release-notes-202608.0.html</guid>
            
            
        </item>
        
        <item>
            <title>Extending components</title>
            <description>&lt;p&gt;With the idea of &lt;a href=&quot;/docs/dg/dev/frontend-development/latest/yves/atomic-frontend/atomic-frontend.html&quot;&gt;atomic design&lt;/a&gt; implemented in Spryker Frontend, you have the possibility to develop each functional element of user interface in a self-contained, isolated container called a component. The frontend design allows you not only to &lt;a href=&quot;/docs/dg/dev/frontend-development/latest/yves/atomic-frontend/managing-components/creating-components.html&quot;&gt;create components&lt;/a&gt; on your own, but also &lt;a href=&quot;/docs/dg/dev/frontend-development/latest/yves/atomic-frontend/managing-components/extending-components.html&quot;&gt;replace&lt;/a&gt; any of them with a component that suits your needs better. But what if you do not want to replace a component? You can create a new component on the basis of an existing one. In this case, you will be able to use both the new component and the source one at the same time.&lt;/p&gt;
&lt;p&gt;Let us review the process of extending a component on the example of &lt;strong&gt;side-drawer&lt;/strong&gt;. This component appears in Spryker Shop only on mobile screens. You can access it by clicking the menu button.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/Tutorials/Introduction/Customize+Frontend/open-side-drawer.png&quot; alt=&quot;Open side drawer&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The following tutorial shows how to create a new component based on the default side drawer. The new side drawer will show an alert whenever it’s present on a page. Also, the component outlook will be different.&lt;/p&gt;
&lt;h2 id=&quot;create-component-folder&quot;&gt;1. Create component folder&lt;/h2&gt;
&lt;p&gt;The first thing we need to do is create a folder for the new component. Since we are going to implement it on the project level, we need to create a folder in &lt;code&gt;src/Pyz/Yves/ShopUi&lt;/code&gt;. The side drawer is an organism, so let us create the following folder: &lt;code&gt;src/Pyz/Yves/ShopUi/Theme/default/components/**organisms**/new-existing-component-side-drawer&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We are going to add new behavior, so the new component will have Javascript code. This requires an entry point for Webpack. To be able to add it, create an empty file named &lt;code&gt;index.ts&lt;/code&gt; in the component folder.&lt;/p&gt;
&lt;h2 id=&quot;override-component-on-the-twig-level&quot;&gt;2. Override component on the twig level&lt;/h2&gt;
&lt;p&gt;No, we need to specify a name for the new component. Also, the component implements its own behavior, so we also need a to use a custom HTML tag to render it. We’ll use the component name as the tag name. Let us create file &lt;code&gt;new-existing-component-side-drawer.twig&lt;/code&gt; and add the &lt;strong&gt;config&lt;/strong&gt; property as follows:&lt;/p&gt;
&lt;div class=&quot;language-twig highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;organism&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;side-drawer&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%}&lt;/span&gt;

&lt;span class=&quot;cp&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;define&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;config&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;new-existing-component-side-drawer&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;new-existing-component-side-drawer&apos;&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;As you can see in the above code, the Twig of the new component extends the original side-drawer component. &lt;code&gt;atom()&lt;/code&gt;, &lt;code&gt;molecule()&lt;/code&gt;, and &lt;code&gt;organism()&lt;/code&gt; accept the module of the extended component as an optional second argument. The side drawer comes from &lt;code&gt;ShopUi&lt;/code&gt;, which is the default, so the argument is omitted here. Pass it when you extend a component from another module, for example &lt;code&gt;molecule(&apos;quick-order-form&apos;, &apos;QuickOrderPage&apos;)&lt;/code&gt;. For more details, see &lt;a href=&quot;/docs/dg/dev/frontend-development/latest/yves/custom-twig-functions-for-yves.html&quot;&gt;Custom Twig functions for Yves&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now, let us customize the template of the source component. The original template is defined in &lt;code&gt;vendor/spryker-shop/shop-ui/src/SprykerShop/Yves/ShopUi/Theme/default/components/organisms/side-drawer/side-drawer.twig&lt;/code&gt;. The only change we are going to add is a different icon in the &lt;strong&gt;close&lt;/strong&gt; block. To do this, add the following to &lt;code&gt;new-existing-component-side-drawer.twig&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;language-twig highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;block&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;close&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%}&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;div&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;config.name&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;}}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;__close&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;a&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;href=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;link link--alt &lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;attributes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;trigger-selector&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;}}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;cp&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;global.close&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;| &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;trans&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;}}&lt;/span&gt;
            &lt;span class=&quot;cp&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;atom&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;icon&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;nv&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;{&lt;/span&gt;
                    &lt;span class=&quot;nv&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;star&apos;&lt;/span&gt;
                &lt;span class=&quot;err&quot;&gt;}&lt;/span&gt;
            &lt;span class=&quot;err&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;only&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%}&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
   &lt;span class=&quot;nt&quot;&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;{%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;endblock&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;change-styles&quot;&gt;3. Change styles&lt;/h2&gt;
&lt;p&gt;Apart from changing the icon, we are going to use different colors. This can be done via styles.&lt;/p&gt;
&lt;p&gt;First of all, we need to inherit the styles of the source component (&lt;em&gt;side-drawer&lt;/em&gt;). It has a mixin called &lt;strong&gt;shop-ui-side-drawer&lt;/strong&gt;. The builder resolves component mixins through its mixin index, so the mixin can be included in any component SCSS file without imports. To render the block, elements and modifiers with the class name of the new component, we need to pass its class name to the mixin.&lt;/p&gt;
&lt;p&gt;The styles of the new component consist of two files: the component SCSS file defines the mixin of the new component, and &lt;code&gt;style.scss&lt;/code&gt; is the style entry point that emits it. Let us create file &lt;code&gt;new-existing-component-side-drawer.scss&lt;/code&gt;, include the original mixin of the &lt;em&gt;side-drawer&lt;/em&gt; component, and pass the class name of the new component as the default value of the &lt;code&gt;$name&lt;/code&gt; parameter:&lt;/p&gt;
&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;@mixin&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new-existing-component-side-drawer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&apos;.new-existing-component-side-drawer&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;@include&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shop-ui-side-drawer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We will change the main and overlay colors. The source mixin emits its &lt;code&gt;@content&lt;/code&gt; block after its own nested rules, so pass the nested rules as content and add the base declarations in a separate rule:&lt;/p&gt;
&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;@mixin&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new-existing-component-side-drawer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&apos;.new-existing-component-side-drawer&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;@include&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shop-ui-side-drawer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;__overlay&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;nl&quot;&gt;background-color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setting-color-main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;err&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setting-color-alt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Now let us create file &lt;code&gt;style.scss&lt;/code&gt;—the style entry point of the new component—and emit the mixin from it. The entry point must not define styles of its own: it only includes the component mixin, wrapped in &lt;code&gt;helper-import&lt;/code&gt; so that the component stays excludable through the &lt;code&gt;$setting-import-blacklist&lt;/code&gt; setting:&lt;/p&gt;
&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;@include&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;helper-import&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;organism&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new-existing-component-side-drawer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;@include&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;new-existing-component-side-drawer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;section class=&apos;info-block info-block--warning&apos;&gt;&lt;i class=&apos;info-block__icon icon-warning&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;
&lt;p&gt;You can find settings for the respective colors in configuration files. They are located in &lt;code&gt;vendor/spryker-shop/shop-ui/src/SprykerShop/Yves/ShopUi/Theme/default/styles/settings&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;p&gt;After defining the styles, let us load them from the component entry point. Open the &lt;code&gt;index.ts&lt;/code&gt; file and add the following content:&lt;/p&gt;
&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Load the component styles&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./style&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;extend-base-styles-with-a-base-hook&quot;&gt;Extend base styles with a base hook&lt;/h3&gt;
&lt;p&gt;Starting from &lt;code&gt;spryker-shop/shop-ui&lt;/code&gt; version 2.0.0 (which ships &lt;a href=&quot;/docs/dg/dev/frontend-development/latest/yves/frontend-builder-for-yves-v2.html&quot;&gt;frontend builder v2&lt;/a&gt;), there is an additional way to customize a core component: &lt;strong&gt;base hooks&lt;/strong&gt;. A base hook lets you add or override declarations in the &lt;em&gt;base&lt;/em&gt; of a core component—everywhere the component is rendered—without copying the component to the project level.&lt;/p&gt;
&lt;p&gt;Base hooks are not limited to ShopUi: the components of the other storefront modules expose them too. The modules were released together with the builder—&lt;code&gt;spryker-shop/shop-ui&lt;/code&gt; as a major version, all the others as minor versions. Modules that ship component styles received the base hooks and the Sass &lt;code&gt;mixed-decls&lt;/code&gt; fix; the remaining ones only update their ShopUi constraint.&lt;/p&gt;
&lt;p&gt;Updating is optional: the builder compiles older module versions as is. Update the modules whose components you customize to at least the following versions:&lt;/p&gt;
&lt;details&gt;&lt;summary&gt;Module versions released with builder v2&lt;/summary&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Module&lt;/th&gt;
&lt;th&gt;Minimum version&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/shop-ui&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^2.0.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker/multi-factor-auth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^2.6.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-feature/ai-commerce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^0.7.8&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-feature/buy-box&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.4.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-feature/order-experience-management&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^0.2.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-feature/purchasing-control&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.2.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-feature/self-service-portal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^20.12.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/agent-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.25.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/agent-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.4.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/availability-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.5.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/barcode-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.1.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/business-on-behalf-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.3.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/calculation-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.4.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/cart-note-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.7.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/cart-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^3.60.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/cart-reorder-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.2.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/catalog-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.37.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/category-image-storage-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.1.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/category-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.6.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/checkout-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^3.43.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/checkout-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.5.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/click-and-collect-page-example&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^0.4.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/cms-block-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^2.5.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/cms-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.9.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/cms-search-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.6.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/comment-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.5.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/company-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^2.37.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/company-user-agent-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.2.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/company-user-invitation-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^2.6.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/company-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.11.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/configurable-bundle-note-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.2.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/configurable-bundle-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.5.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/configurable-bundle-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.10.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/content-navigation-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.7.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/content-product-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.5.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/currency-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.7.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/customer-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^2.83.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/customer-reorder-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^6.18.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/date-time-configurator-page-example&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^0.8.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/discount-promotion-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^3.8.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/discount-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.10.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/error-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.12.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/file-manager-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^2.2.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/gift-card-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.3.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/home-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.3.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/language-switcher-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.9.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/merchant-product-offer-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^2.9.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/merchant-product-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.8.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/merchant-profile-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.3.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/merchant-registration-request-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.1.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/merchant-relation-request-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.3.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/merchant-relation-request-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.1.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/merchant-relationship-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.1.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/merchant-relationship-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.1.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/merchant-sales-return-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.2.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/merchant-search-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.1.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/merchant-switcher-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^0.9.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/merchant-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.6.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/money-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.8.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/multi-cart-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^2.9.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/multi-cart-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.11.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/newsletter-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.3.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/newsletter-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.9.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/order-cancel-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.2.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/order-custom-reference-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.2.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/payment-app-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.4.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/persistent-cart-share-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.4.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/price-product-volume-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.10.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/price-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.5.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-alternative-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.7.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-barcode-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.2.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-bundle-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.9.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-category-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.10.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-comparison-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.1.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-comparison-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.1.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-configuration-cart-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.1.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-configuration-shopping-list-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.1.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-configuration-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.1.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-configuration-wishlist-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.1.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-detail-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^3.33.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-group-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.13.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-image-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.1.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-label-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.7.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-measurement-unit-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.5.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-new-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.5.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-offer-service-point-availability-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.3.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-option-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.6.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-packaging-unit-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.9.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-relation-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.5.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-replacement-for-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.8.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-review-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.20.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-search-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^3.8.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-set-detail-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.12.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-set-list-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.3.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-set-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.11.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/product-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.7.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/quick-order-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^4.15.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/quote-approval-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.6.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/quote-request-agent-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^3.6.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/quote-request-agent-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^2.7.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/quote-request-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^3.7.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/quote-request-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^2.6.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/sales-configurable-bundle-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.7.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/sales-order-amendment-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.2.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/sales-order-threshold-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.2.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/sales-product-bundle-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.3.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/sales-product-configuration-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.2.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/sales-return-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.12.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/sales-service-point-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.3.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/service-point-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.8.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/shared-cart-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^2.6.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/shared-cart-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.8.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/shipment-type-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.6.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/shopping-list-note-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.2.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/shopping-list-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.11.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/shopping-list-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.7.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/tabs-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.1.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/traceable-event-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.3.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/wishlist-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.15.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-shop/wishlist-widget&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;^1.4.0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/details&gt;
&lt;p&gt;Every ShopUi component mixin includes an optional hook mixin at the top of its base block, before any element and modifier rules:&lt;/p&gt;
&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;@mixin&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shop-ui-side-drawer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&apos;.side-drawer&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;err&quot;&gt;#&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;$name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;err&quot;&gt;@if&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;meta.mixin-exists(shop-ui-side-drawer-base-hook)&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;err&quot;&gt;@include&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;shop-ui-side-drawer-base-hook;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;//&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;element&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;modifier&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;rules&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;To use it, define a mixin named &lt;code&gt;&amp;lt;component-mixin-name&amp;gt;-base-hook&lt;/code&gt; in a project-level component SCSS file:&lt;/p&gt;
&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;@mixin&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;shop-ui-side-drawer-base-hook&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;setting-color-alt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The builder’s mixin index picks the definition up automatically and wires it into the core component at compile time—no imports needed. The declarations are emitted inside the component’s base selector, before its nested rules.&lt;/p&gt;
&lt;section class=&apos;info-block &apos;&gt;&lt;i class=&apos;info-block__icon icon-info&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;&lt;div class=&quot;info-block__title&quot;&gt;Why base hooks exist&lt;/div&gt;
&lt;p&gt;Base hooks fix a Sass cascade problem. Styles added through the component mixin’s body (the &lt;code&gt;@content&lt;/code&gt; block) are emitted &lt;em&gt;after&lt;/em&gt; the component’s nested rules, such as &lt;code&gt;&amp;amp;__overlay&lt;/code&gt; or &lt;code&gt;&amp;amp;--show&lt;/code&gt;. Since Sass 1.92, declarations that follow nested rules are no longer hoisted to the top of the parent rule (the &lt;code&gt;mixed-decls&lt;/code&gt; deprecation): they stay in source order, which triggers deprecation warnings and can flip which rule wins at equal specificity—base declarations placed after a modifier would override the modifier.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;spryker-shop/shop-ui&lt;/code&gt; 2.0.0, the core styles were fixed to emit base declarations before nested rules, and base hooks give project code a safe place to contribute base declarations in the correct position. Unlike the v1 builder, builder v2 doesn’t silence Sass deprecation warnings, so any remaining &lt;code&gt;mixed-decls&lt;/code&gt; cases in your project code are visible in the build output and should be fixed the same way.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;p&gt;Use the base hook when you want to change the base styles of the original component itself. Use the mixin-include approach described above when you’re building a new component based on an existing one.&lt;/p&gt;
&lt;h2 id=&quot;modify-behavior&quot;&gt;4. Modify behavior&lt;/h2&gt;
&lt;p&gt;Finally, let us define what the component does. Create the &lt;code&gt;new-existing-component-side-drawer.ts&lt;/code&gt;file with the following content:&lt;/p&gt;
&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Import class SideDrawer&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SideDrawer&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;ShopUi/components/organisms/side-drawer/side-drawer&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// export the extended class&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;NewSideDrawer&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;SideDrawer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kr&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;super&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;nx&quot;&gt;alert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;New side drawer&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In the above example, first, we import class &lt;strong&gt;SideDrawer&lt;/strong&gt; from the global level. After that, we export a new class, &lt;code&gt;NewSideDrawer&lt;/code&gt;. Since it extends the class of the default side drawer component, it also inherits its behavior.&lt;/p&gt;
&lt;section class=&apos;info-block info-block--warning&apos;&gt;&lt;i class=&apos;info-block__icon icon-warning&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;
&lt;p&gt;If you want to define the component behavior from scratch rather than importing the behavior of a default component, you need to extend the base Component class instead.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;ShopUi/models/component&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;NewSideDrawer&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Component&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt; &lt;/span&gt; &lt;span class=&quot;err&quot;&gt; &lt;/span&gt; &lt;span class=&quot;err&quot;&gt; &lt;/span&gt; &lt;span class=&quot;err&quot;&gt; &lt;/span&gt; &lt;span class=&quot;err&quot;&gt; &lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;// TODO: your code here&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;After implementing the component behavior, let us register it to the HTML tag of the new component. The tag name was defined in Twig on step &lt;strong&gt;2&lt;/strong&gt;. Open the &lt;code&gt;index.ts&lt;/code&gt; file again and add the following content:&lt;/p&gt;
&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Import the &apos;register&apos; function from the Shop Application&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;register&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;ShopUi/app/registry&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Register the component&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// (in thei example, the original component tag is side-drawer)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;new-existing-component-side-drawer&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;/* webpackMode: &quot;eager&quot; */&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;./new-existing-component-side-drawer&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;When importing the component, the &lt;strong&gt;eager&lt;/strong&gt; keyword is used, as the component is used on every page, and we want it to be always available and loaded.&lt;/p&gt;
&lt;h2 id=&quot;build-frontend&quot;&gt;5. Build frontend&lt;/h2&gt;
&lt;p&gt;Now, let us build the frontend: &lt;code&gt;npm run yves&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As soon as the frontend has been compiled, replace the original side drawer with the new implementation. To do this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Copy the file &lt;code&gt;vendor/spryker-shop/shop-ui/src/SprykerShop/Yves/ShopUi/Theme/default/page-layout-main/page-layout-main.twig&lt;/code&gt; to &lt;code&gt;src/Pyz/Yves/ShopUi/Theme/default/page-layout-main/page-layout-main.twig&lt;/code&gt;. Doing so overrides the default main page on the project level.&lt;/li&gt;
&lt;li&gt;Open the copied file.&lt;/li&gt;
&lt;li&gt;Replace the following line: &lt;code&gt;{% include organism(&apos;side-drawer&apos;) with {&lt;/code&gt; with this one:&lt;code&gt;{% include organism(&apos;new-existing-component-side-drawer&apos;) with {&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Save the file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, whenever you access a page with a side drawer in Spryker Shop, you will get an alert from the new side drawer.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/Tutorials/Introduction/Customize+Frontend/side-drawer-notification.png&quot; alt=&quot;Side drawer&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Also, the drawer itself has a new outlook.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/Tutorials/Introduction/Customize+Frontend/new-side-drawer.png&quot; alt=&quot;New side drawer&quot; /&gt;&lt;/p&gt;
</description>
            <pubDate>Thu, 06 Aug 2026 10:38:03 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/frontend-development/latest/yves/atomic-frontend/managing-components/extending-components.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/frontend-development/latest/yves/atomic-frontend/managing-components/extending-components.html</guid>
            
            
        </item>
        
    </channel>
</rss>
