<?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>Sun, 21 Jun 2026 15:30:43 +0000</lastBuildDate>
        <generator>Jekyll v4.2.2</generator>
        
        
        <item>
            <title>Transfer data between Yves and Zed</title>
            <description>Yves gets most of its data from the client-side NoSQL data stores (data such as product details, product categories, and prices). There are situations when Yves needs to communicate with Zed either to submit data (for example, the customer has submitted a new order or subscribed to a newsletter) or to retrieve data (for example, order history for the customer or customer account details).

This document shows how to set up communication between Yves and Zed and display a random salutation message that is retrieved from Zed.

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

You need a module for which you set up communication between Yves and Zed. To add the module, see [Add a new module](/docs/dg/dev/backend-development/extend-spryker/create-modules.html).

{% endinfo_block %}

To implement communication between Yves and Zed, follow the steps below.

## 1. Create a transfer object

Communication between Yves and Zed is done using [transfer objects](/docs/dg/dev/backend-development/data-manipulation/data-ingestion/structural-preparations/create-use-and-extend-the-transfer-objects.html). So to establish communication between Yves and Zed, you need to create a transfer object as follows:

1. Create a new transfer object and add it to the `src/Pyz/Shared/HelloWorld/Transfer/` folder. In the example, it&apos;s called the `helloworld.transfer.xml` transfer object, and one property is assigned to it:

```xml
&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;transfers xmlns=&quot;spryker:transfer-01&quot;
    xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
    xsi:schemaLocation=&quot;spryker:transfer-01 http://static.spryker.com/transfer-01.xsd&quot;&gt;

    &lt;transfer name=&quot;HelloWorldMessage&quot;&gt;
        &lt;property name=&quot;value&quot; type=&quot;string&quot; /&gt;
    &lt;/transfer&gt;

&lt;/transfers&gt;
```

2. Generate the transfer object so that it&apos;s ready to be used:

```bash
console transfer:generate
```

1. Add an operation to your `HelloWorldFacade` that returns a random salutation message using the transfer object you&apos;ve just defined:

```php
&lt;?php
/**
 * @return \Generated\Shared\Transfer\HelloWorldMessageTransfer
 */
public function getSalutationMessage()
{
    return $this-&gt;getFactory()-&gt;createMessageGenerator()-&gt;generateHelloMessage();
}
```


## 2. Create a gateway controller

The `GatewayController` controller is responsible for communication with Yves. It must extend the `AbstractGatewayController` class. So your next step is to create the controller.

Create the `GatewayController` in Zed under `Pyz\Zed\HelloWorld\Communication\Controller`. Add an action to this controller that calls the functionality you have exposed through your facade:

```php
&lt;?php
namespace Pyz\Zed\HelloWorld\Communication\Controller;

use Spryker\Zed\Kernel\Communication\Controller\AbstractGatewayController;

/**
 * @method \Pyz\Zed\HelloWorld\Business\HelloWorldFacade getFacade()
 */
class GatewayController extends AbstractGatewayController
{
    /**
     *  @return \Generated\Shared\Transfer\HelloWorldMessageTransfer
     */
    public function getSalutationMessageAction()
    {
        return $this-&gt;getFacade()
                    -&gt;getSalutationMessage();
    }
}
```

## 3. Implement the stub

Move to the client part to add support for calling the added controller action and follow these steps:

1. In `src/Pyz/Client/HelloWorld/Zed`, create a `HelloWorldStub` stub. This stub lets you submit an HTTP request to Zed.

&lt;details&gt;
&lt;summary&gt;Pyz\Client\HelloWorld\Zed&lt;/summary&gt;

```php
&lt;?php

namespace Pyz\Client\HelloWorld\Zed;

use Generated\Shared\Transfer\HelloWorldMessageTransfer;
use Spryker\Client\ZedRequest\ZedRequestClientInterface;

class HelloWorldStub implements HelloWorldStubInterface
{

    /**
     * @var \Spryker\Client\ZedRequest\ZedRequestClientInterface
     */
    protected $zedRequestClient;

    /**
     * @param \Spryker\Client\ZedRequest\ZedRequestClientInterface $zedRequestClient
     */
    public function __construct(ZedRequestClientInterface $zedRequestClient)
    {
        $this-&gt;zedRequestClient = $zedRequestClient;
    }

    /**
     * @return \Generated\Shared\Transfer\HelloWorldMessageTransfer
     */
    public function getSalutationMessage()
    {
        return $this-&gt;zedRequestClient-&gt;call(
            &apos;/hello-world/gateway/get-salutation-message&apos;,
            new HelloWorldMessageTransfer()
        );
    }

}
```

&lt;/details&gt;

{% info_block infoBox &quot;Request parameter&quot; %}

Through the second parameter, you can pass a transfer object as a request parameter to the request client call.

{% endinfo_block %}

2. Add a corresponding interface for the stub (`HelloWorldStubInterface`). The interface should contain the `getSalutationMessage()` method defined.

{% info_block infoBox %}

In the example, the stub depends on `ZedRequestClient` that can be provided by implementing `HelloWorldDependencyProvider`:

&lt;details&gt;
&lt;summary&gt;Pyz\Client\HelloWorld&lt;/summary&gt;

```php
&lt;?php

namespace Pyz\Client\HelloWorld;

use Spryker\Client\Customer\CustomerDependencyProvider as SprykerCustomerDependencyProvider;
use Spryker\Client\Kernel\Container;

class HelloWorldDependencyProvider extends SprykerCustomerDependencyProvider
{

    const CLIENT_ZED_REQUEST = &apos;CLIENT_ZED_REQUEST&apos;;

    /**
     * @param \Spryker\Client\Kernel\Container $container
     *
     * @return \Spryker\Client\Kernel\Container
     */
    public function provideServiceLayerDependencies(Container $container)
    {
        $container = $this-&gt;addZedRequestClient($container);

        return $container;
    }

    /**
     * @param \Spryker\Client\Kernel\Container $container
     *
     * @return \Spryker\Client\Kernel\Container
     */
    protected function addZedRequestClient(Container $container)
    {
        $container[self::CLIENT_ZED_REQUEST] = function (Container $container) {
            return $container-&gt;getLocator()-&gt;zedRequest()-&gt;client();
        };

        return $container;
    }

}
```

&lt;/details&gt;

{% endinfo_block %}

3. To get an instance of `HelloWorldStub`, create `HelloWorldFactory`:

```php
&lt;?php

namespace Pyz\Client\HelloWorld;

use Pyz\Client\HelloWorld\Zed\HelloWorldStub;
use Spryker\Client\Kernel\AbstractFactory;

class HelloWorldFactory extends AbstractFactory
{

    /**
     * @return \Pyz\Client\HelloWorld\Zed\HelloWorldStubInterface
     */
    public function createZedStub()
    {
        return new HelloWorldStub($this-&gt;getZedRequestClient());
    }

    /**
     * @return \Spryker\Client\ZedRequest\ZedRequestClientInterface
     */
    protected function getZedRequestClient()
    {
        return $this-&gt;getProvidedDependency(HelloWorldDependencyProvider::CLIENT_ZED_REQUEST);
    }

}
```

## 4. Implement the client

Now you can create the client that consumes this service.

In `src/Pyz/Client/HelloWorld`, create the `HelloWorldClient` client together with its corresponding interface.

```php
&lt;?php
namespace Pyz\Client\HelloWorld;

use Spryker\Client\Kernel\AbstractClient;

/**
 * @method \Pyz\Client\HelloWorld\HelloWorldFactory getFactory()
 */
class HelloWorldClient extends AbstractClient implements HelloWorldClientInterface
{
    /**
     *
     * @return \Generated\Shared\Transfer\HelloWorldMessageTransfer
     */
    public function getSalutationMessage()
    {
        return $this-&gt;getFactory()
                    -&gt;createZedStub()
                    -&gt;getSalutationMessage();
    }
}
```

## 5. Create a controller and a view in Yves

Now you can move to Yves and create the controller and the Twig template that renders a random message:

1. In `src/Pyz/Yves/HelloWorld/Controller`, create `IndexController`:

```php
&lt;?php
namespace Pyz\Yves\HelloWorld\Controller;

use Spryker\Yves\Kernel\Controller\AbstractController;

/**
 * @method \Pyz\Client\HelloWorld\HelloWorldClientInterface getClient()
 */
class IndexController extends AbstractController
{
    /**
     * @return array
     */
    public function indexAction()
    {

        return [
            &apos;salutationMessage&apos; =&gt; $this-&gt;getClient()-&gt;getSalutationMessage()
        ];
    }

}
```

2. In `src/Pyz/Yves/HelloWorld/Theme/default/index`, create the `index.twig` file:

```php
{% raw %}{%{% endraw %} extends &quot;@application/layout/layout.twig&quot; {% raw %}%}{% endraw %}

{% raw %}{%{% endraw %} block content {% raw %}%}{% endraw %}
    {% raw %}{{{% endraw %} salutationMessage.value {% raw %}}}{% endraw %}
{% raw %}{%{% endraw %} endblock {% raw %}%}{% endraw %}
```

## 6. Set up the URL routing

1. In `src/Pyz/Yves/HelloWorld/Plugin/Route`, add `HelloWorldRouteProviderPlugin`:

```php
&lt;?php
namespace Pyz\Yves\HelloWorld\Plugin\Router;

use Spryker\Yves\Router\Plugin\RouteProvider\AbstractRouteProviderPlugin;
use Spryker\Yves\Router\Route\RouteCollection;

class HelloWorldRouteProviderPlugin extends AbstractRouteProviderPlugin
{
    protected const ROUTE_HELLO_WORD = &apos;hello-word&apos;;

    /**
     * @param \Spryker\Yves\Router\Route\RouteCollection $routeCollection
     *
     * @return \Spryker\Yves\Router\Route\RouteCollection
     */
    protected function addNewProductsRoute(RouteCollection $routeCollection): RouteCollection
    {
        $route = $this-&gt;buildRoute(&apos;/hello&apos;, &apos;HelloWorld&apos;, &apos;index&apos;, &apos;indexAction&apos;);
        $routeCollection-&gt;add(static::ROUTE_HELLO_WORD, $route);

        return $routeCollection;
    }
}
```

2. Register your route provider plugin under `RouterDependencyProvider`:

```php
protected function getRouteProvider($isSsl)
	  {
		 return [
			//...
			 new HelloWorldRouteProviderPlugin(),
		 ];
	  }
```

3. Clear cache for routes:

```bash
vendor/bin/console router:cache:warm-up
```

That&apos;s it! `https://mysprykershop.com/hello` now displays a random salutation message.

## ZedRequest header

Since [ZedRequest 3.16.0](https://github.com/spryker/zed-request/releases/tag/3.16.0), you can alter the headers sent with each ZedRequest. You can either use the *default header plugins* or *create your own* by using the `\Spryker\Client\ZedRequestExtension\Dependency\Plugin\HeaderExpanderPluginInterface`.

### Default header plugins

You can use the following default header plugins:

- `\Spryker\Client\ZedRequest\Plugin\AcceptEncodingHeaderExpanderPlugin`, adds the `Accept-Encoding` header to the request.
- `\Spryker\Client\ZedRequest\Plugin\AuthTokenHeaderExpanderPlugin`, adds the `Auth-Token` header to the request.
- `\Spryker\Client\ZedRequest\Plugin\RequestIdHeaderExpanderPlugin`, adds the `X-Request-ID` header to the request.

These plugins can be added to `\Pyz\Client\ZedRequest\ZedRequestDependencyProvider::getHeaderExpanderPlugins()`.

### Create your own header plugin

You can create your own header expander plugin with the `\Spryker\Client\ZedRequestExtension\Dependency\Plugin\HeaderExpanderPluginInterface`.
For example, if you need a header with the name `Project-Name`, you just create a plugin like this:

```php
&lt;?php

namespace Pyz\Client\ZedRequest\Plugin;

use Spryker\Client\Kernel\AbstractPlugin;
use Spryker\Client\ZedRequestExtension\Dependency\Plugin\HeaderExpanderPluginInterface;

class ProjectNameHeaderExpanderPlugin extends AbstractPlugin implements HeaderExpanderPluginInterface
{
    /**
     * @param array $headers
     *
     * @return array
     */
    public function expandHeader(array $headers): array
    {
        $headers[&apos;Project-Name&apos;] = &apos;My project name&apos;;

        return $headers;
    }
}
```

After adding this plugin to `\Pyz\Client\ZedRequest\ZedRequestDependencyProvider::getHeaderExpanderPlugins()`, your new header is used with every `ZedRequest`.

## Enable Base64 encoding for ZedRequest transfers

Some infrastructure configurations, such as AWS WAF rules, reject requests that contain certain character sequences—for example, `../`—causing Yves-to-Zed communication to fail. Enabling Base64 encoding for ZedRequest transfers ensures that the payload does not contain such sequences, preventing these rejections.

To enable Base64 encoding, in `config/Shared/config_default.php`, add the following:

```php
use Spryker\Shared\ZedRequest\ZedRequestConstants;

$config[ZedRequestConstants::TRANSFER_BASE64_ENCODING_ENABLED] = true;
```

When enabled, Spryker Base64-encodes the transfer object payload before sending it from Yves to Zed and automatically decodes it on the Zed side.
</description>
            <pubDate>Fri, 19 Jun 2026 12:58:22 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/backend-development/data-manipulation/data-interaction/transfer-data-between-yves-and-zed.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/backend-development/data-manipulation/data-interaction/transfer-data-between-yves-and-zed.html</guid>
            
            
        </item>
        
        <item>
            <title>Identity Access Management</title>
            <description>The Identity Access Management capability enables all types of users in a Spryker shop to create and manage accounts. Different levels of security let users manage the access of other users.

{% info_block infoBox &quot;Federated Authentication via OAuth2/OIDC&quot; %}

Spryker now supports [Federated Authentication](/docs/pbc/all/oauth/latest/federated-authentication.html) — a modern alternative to managing separate Spryker credentials. Instead of maintaining a standalone identity per application, your users log in through the Identity Provider your organization already uses.

Key benefits:

- **Works across all Spryker applications**: Storefront, Back Office, and Merchant Portal are all supported out of the box.
- **Broad provider support**: Connect any OAuth2/OIDC-compatible IdP — Keycloak, Microsoft Entra ID, Okta, Auth0, Google Workspace, Salesforce, Amazon Cognito, and more.
- **Multiple providers on Storefront and Merchant Portal**: You can connect as many IdPs as your users need, with a dedicated login button for each.

{% endinfo_block %}

## Back Office authentication

Back Office supports login via two types of accounts:

- Back Office account.
- Account of a third-party service that is configured as a single sign-on.

### Login with a Back Office account

Only an existing Back Office user with sufficient permissions or a developer can create Back Office accounts. That is, if you want to onboard a new Back Office user, you need to create an account for them. For instructions on creating accounts in the Back Office, see [Create users](/docs/pbc/all/user-management/latest/base-shop/manage-in-the-back-office/manage-users/create-users.html).


To log in, on the Back Office login page, a user enters the email address and password of their account. If the credentials are correct and their account is active at that time, they are logged in.

If a user does not remember their password, they can reset it using the form available on the login page.

### Login with a single sign-on

Your project can have a single sign-on(SSO) login configured for the Back Office. SSO lets users log into the Back Office with accounts of a third-party service.

The feature is shipped with an exemplary ECO module that supports SSO authentication via Microsoft Azure Active Directory. With the existing infrastructure, you can develop your own ECO modules for the identity managers you need. SSO uses the [OpenID](https://en.wikipedia.org/wiki/OpenID) protocol for authentication.

To log in with an SSO, on the Back Office login page, users click **Login with {Third-party service name}**. This opens the sign-in page of the configured service. Users sign in with their accounts and get redirected to the Back Office.

If a user chooses to log in using a third party, they are redirected to the OAuth provider&apos;s sign-in page—for example, Microsoft Azure. If the user successfully signs into the third-party service, the check is made if the user exists in the Spryker database. If the user exists in the database and is active, the user is logged in.

The following sections describe different ways of handling users that have access to your SSO service, but don&apos;t have a Back Office account.

### SSO authentication strategies

Depending on your access and security requirements, you can have one of the following strategies implemented for SSO authentication.


#### Registration is required only with the SSO service

If a user logs in with an SSO but does not have a Back Office account, the following happens:
- Based on the third-party system&apos;s user data, such as first name, last name, and email, a Back Office account is created.
- The user is assigned to the default user group.
- The user is logged into the Back Office.

The login process looks like this:

![image](https://confluence-connect.gliffy.net/embed/image/5b0f6ab5-d4d5-4b53-b82a-d73bec9c81ea.png?utm_medium=live&amp;utm_source=custom)

#### Registration is required with the SSO service and with Spryker

If a user logs in with an SSO but does not have a Back Office account, the user is not logged in. To be able to log in, a user must have a Back Office account registered using the email address used for their account with the SSO service. Usually, this strategy is used when not all the users that have access to the SSO service need access to the Back Office.

## Post-login redirect

When a user&apos;s session times out, they are redirected to the login page. Without post-login redirect, after re-authentication, users land on the homepage and lose their context. With post-login redirect enabled, users are automatically sent back to the exact page they were on before the session expired — preserving their workflow across Back Office, Merchant Portal, and Storefront.

The last visited page URL is tracked on every eligible GET request and stored in a browser cookie named `last-visited-page`. AJAX requests, login and logout pages, and internal framework paths are excluded from tracking.

If the user was on a create or edit page before the session expired, they are redirected back to that page, but any unsaved data is lost.

Post-login redirect does not apply to agent users in Storefront and Merchant Portal. Redirect across multiple browsers or devices is not supported. The redirect applies only to the browser in which the original session was active.

## Glue API authentication

For details about Glue API authentication, see [Glue API authentication and authorization](/docs/integrations/spryker-glue-api/authenticating-and-authorization/authenticating-and-authorization.html)

## Current constraints

The feature has the following functional constraint:

Each of the identity managers is an ECO module that must be developed separately. After the module development, the identity manager&apos;s roles and permissions must be mapped to the roles and permissions in Spryker. The mapping is always implemented at the project level.



## Related Developer documents

|INSTALLATION GUIDES  | GLUE API GUIDES |
| - | - |
| [Install the Spryker Core Back Office feature](/docs/pbc/all/identity-access-management/latest/install-and-upgrade/install-the-spryker-core-back-office-feature.html)  | [Authentication and authorization](/docs/integrations/spryker-glue-api/authenticating-and-authorization/authenticating-and-authorization.html) |
| [Install Microsoft Azure Active Directory](/docs/pbc/all/identity-access-management/latest/install-and-upgrade/install-microsoft-azure-active-directory.html)   | [Authentication and authorization](/docs/integrations/spryker-glue-api/authenticating-and-authorization/authenticating-and-authorization.html) |
| [Install the Customer Access Glue API](/docs/pbc/all/identity-access-management/latest/install-and-upgrade/install-the-customer-access-glue-api.html) | [Create customers](/docs/pbc/all/identity-access-management/latest/manage-using-glue-api/glue-api-create-customers.html) |
| [Integrate post-login redirect](/docs/pbc/all/identity-access-management/latest/install-and-upgrade/integrate-post-login-redirect.html) | |
| | [Federated Authentication via OAuth2/OIDC](/docs/pbc/all/oauth/latest/federated-authentication.html) |
| | [Confirm customer registration](/docs/pbc/all/identity-access-management/latest/manage-using-glue-api/glue-api-confirm-customer-registration.html) |
| | [Manage customer passwords](/docs/pbc/all/identity-access-management/latest/manage-using-glue-api/glue-api-manage-customer-passwords.html) |
| | [Authenticate as a customer](/docs/pbc/all/identity-access-management/latest/manage-using-glue-api/glue-api-authenticate-as-a-customer.html) |
| | [Manage customer authentication tokens via OAuth 2.0](/docs/pbc/all/identity-access-management/latest/manage-using-glue-api/glue-api-manage-customer-authentication-tokens-via-oauth-2.0.html) |
| | [Manage customer authentication tokens](/docs/pbc/all/identity-access-management/latest/manage-using-glue-api/glue-api-manage-customer-authentication-tokens.html) |
| | [Authenticate as a Back Office user](/docs/pbc/all/identity-access-management/latest/manage-using-glue-api/glue-api-authenticate-as-a-back-office-user.html) |
| | [Authenticating as a company user](/docs/pbc/all/identity-access-management/latest/manage-using-glue-api/glue-api-authenticate-as-a-company-user.html) |
| | [Manage company user authentication tokens](/docs/pbc/all/identity-access-management/latest/manage-using-glue-api/glue-api-manage-company-user-authentication-tokens.html) |
| | [Authenticate as an agent assist](/docs/pbc/all/identity-access-management/latest/manage-using-glue-api/glue-api-authenticate-as-an-agent-assist.html) |
| | [Managing agent assist authentication tokens](/docs/pbc/all/identity-access-management/latest/manage-using-glue-api/glue-api-manage-agent-assist-authentication-tokens.html) |
| | [Delete expired refresh tokens](/docs/pbc/all/identity-access-management/latest/manage-using-glue-api/glue-api-delete-expired-refresh-tokens.html) |
</description>
            <pubDate>Fri, 19 Jun 2026 12:08:07 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/identity-access-management/latest/identity-access-management.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/identity-access-management/latest/identity-access-management.html</guid>
            
            
        </item>
        
        <item>
            <title>Enable Dynamic Multistore</title>
            <description>&lt;p&gt;This document describes how to enable &lt;a href=&quot;/docs/pbc/all/dynamic-multistore/latest/base-shop/dynamic-multistore-feature-overview.html&quot;&gt;Dynamic Multistore&lt;/a&gt; (DMS).&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;If your project version is below 202307.0, &lt;a href=&quot;/docs/pbc/all/dynamic-multistore/latest/base-shop/install-and-upgrade/install-features/install-dynamic-multistore.html&quot;&gt;Install Dynamic Multistore&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;enable-dynamic-multistore&quot;&gt;Enable Dynamic Multistore&lt;/h3&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;Staging environment&lt;/div&gt;
To avoid unexpected downtime and data loss, perform and test *all* of the following steps in a staging environment first.
&lt;/div&gt;&lt;/section&gt;
&lt;ol&gt;
&lt;li&gt;Replace &lt;code&gt;StoreClient::getCurrentStore()&lt;/code&gt; and &lt;code&gt;StoreFacade::getCurrentStore()&lt;/code&gt; methods with &lt;code&gt;StoreStorageClient:getStoreNames()&lt;/code&gt;, &lt;code&gt;StoreStorageClient::findStoreByName()&lt;/code&gt;, or &lt;code&gt;StoreFacade::getStoreCollection()&lt;/code&gt; in the following:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Back Office&lt;/li&gt;
&lt;li&gt;Merchant Portal&lt;/li&gt;
&lt;li&gt;Console Commands&lt;/li&gt;
&lt;li&gt;Gateway&lt;/li&gt;
&lt;li&gt;BackendAPI&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Update custom console commands to meet the following rules:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Store(Facade|Client)::getCurrentStore()&lt;/code&gt; isn’t used in the code a console command executes.&lt;/li&gt;
&lt;li&gt;All store-aware commands implement &lt;code&gt;Spryker\Zed\Kernel\Communication\Console\StoreAwareConsole&lt;/code&gt; and execute actions for a specific store if a store parameter is provided; if not provided, actions are executed for all the stores in the region.&lt;/li&gt;
&lt;li&gt;Optional: We recommend using the &lt;code&gt;--store&lt;/code&gt; parameter instead of &lt;code&gt;APPLICATION_STORE&lt;/code&gt; env variable; both methods are supported.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;
&lt;p&gt;After enabling DMS, the basic domain structure must change from store to region for all the applications. For example, &lt;code&gt;https://yves.de.mysprykershop.com&lt;/code&gt; will change to &lt;code&gt;https://yves.eu.mysprykershop.com&lt;/code&gt;. To prevent negative SEO effects, set up the needed redirects. If your target domain doesn’t change, for example it doesn’t contain a region name - yves.mysprykershop.com - you may skip this step.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update AWS deployment files to DMS mode using the example:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Original environment variables section:&lt;/p&gt;
&lt;div class=&quot;language-yaml 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;SPRYKER_HOOK_BEFORE_DEPLOY&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;vendor/bin/install&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-r&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;pre-deploy.dynamic-store-off&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-vvv&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_HOOK_AFTER_DEPLOY&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;true&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_HOOK_INSTALL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;vendor/bin/install&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-r&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;production.dynamic-store-off&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--no-ansi&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-vvv&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_HOOK_DESTRUCTIVE_INSTALL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;vendor/bin/install&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-r&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;destructive.dynamic-store-off&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--no-ansi&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-vvv&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_YVES_HOST_DE&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;de.&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_YVES_HOST_AT&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;at.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Updated environment variables section:&lt;/p&gt;
&lt;div class=&quot;language-yaml 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;SPRYKER_HOOK_BEFORE_DEPLOY&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;vendor/bin/install&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-r&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;pre-deploy&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-vvv&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_HOOK_AFTER_DEPLOY&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;true&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_HOOK_INSTALL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;vendor/bin/install&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-r&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;dynamic-store&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--no-ansi&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-vvv&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_HOOK_DESTRUCTIVE_INSTALL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;vendor/bin/install&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-r&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;destructive&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--no-ansi&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-vvv&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_DYNAMIC_STORE_MODE&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_YVES_HOST_EU&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yves.eu.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Original regions section:&lt;/p&gt;
&lt;div class=&quot;language-yaml 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;regions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&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_queue&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;de_search&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;session&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;2&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_queue&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;3&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;at_search&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;session&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;4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Updated regions section:&lt;/p&gt;
&lt;div class=&quot;language-yaml 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;regions&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;eu-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;eu_search&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;
&lt;p&gt;&lt;strong&gt;Before first deployment&lt;/strong&gt;, create a support case and provide the deploy.ABC.yml file that contains the dynamic multistore setup. For example: “We have added the dynamic multistore setup to our deploy.ABC.yml and would like it to be activated.”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;DMS changes the structure of RabbitMQ messages. When you’re ready for the migration, wait for all the remaining messages in the queue to be processed. When the queue is empty, enable the maintenance mode.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The downtime associated with the maintenance mode is limited to the deployment time, which usually takes up to an hour.&lt;/p&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;Run a normal deploy for your server pipeline.&lt;/li&gt;
&lt;/ol&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;Verification&lt;/div&gt;
- Make sure your store is available at `https://yves.eu.mysprykershop.com` or `https://backoffice.eu.mysprykershop.com`.
- Make sure the store switcher is displayed on the Storefront.
&lt;/div&gt;&lt;/section&gt;
&lt;p&gt;Your shop is now running in DMS mode.&lt;/p&gt;
&lt;h3 id=&quot;check-if-dynamic-multistore-is-enabled&quot;&gt;Check if Dynamic Multistore is enabled&lt;/h3&gt;
&lt;p&gt;DMS is enabled if at least one of the following applies:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Using environment: If the value of &lt;code&gt;SPRYKER_DYNAMIC_STORE_MODE&lt;/code&gt; environment variable is &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Using interface: In the Back Office &amp;gt; &lt;strong&gt;Administration&lt;/strong&gt; &amp;gt; &lt;strong&gt;Stores&lt;/strong&gt;, an &lt;strong&gt;Edit&lt;/strong&gt; button is displayed next to each store.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;disable-dynamic-multistore&quot;&gt;Disable Dynamic Multistore&lt;/h2&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;Staging environment&lt;/div&gt;
To avoid unexpected downtime and data loss, perform and test *all* of the following steps in a staging environment first.
&lt;/div&gt;&lt;/section&gt;
&lt;ol&gt;
&lt;li&gt;After disabling DMS, the basic domain structure will change from region to store for all the applications. To prevent negative SEO effects, set up the needed redirects.&lt;/li&gt;
&lt;li&gt;DMS changes the structure of RabbitMQ messages. When you’re ready for the migration, wait for all the remaining messages in the queue to be processed. When the queue is empty, enable the maintenance mode.
(Expected downtime is limited to the deployment time, normally it takes less than 1hr)&lt;/li&gt;
&lt;li&gt;Revert changes in deploy files to disable DMS:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Original environment variables section:&lt;/p&gt;
&lt;div class=&quot;language-yaml 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;SPRYKER_HOOK_BEFORE_DEPLOY&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;vendor/bin/install&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-r&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;pre-deploy&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-vvv&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_HOOK_AFTER_DEPLOY&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;true&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_HOOK_INSTALL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;vendor/bin/install&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-r&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;dynamic-store&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--no-ansi&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-vvv&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_HOOK_DESTRUCTIVE_INSTALL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;vendor/bin/install&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-r&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;destructive&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--no-ansi&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-vvv&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_DYNAMIC_STORE_MODE&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_YVES_HOST_EU&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;yves.eu.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Updated environment variables section:&lt;/p&gt;
&lt;div class=&quot;language-yaml 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;SPRYKER_HOOK_BEFORE_DEPLOY&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;vendor/bin/install&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-r&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;pre-deploy.dynamic-store-off&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-vvv&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_HOOK_AFTER_DEPLOY&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;true&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_HOOK_INSTALL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;vendor/bin/install&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-r&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;production.dynamic-store-off&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--no-ansi&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-vvv&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_HOOK_DESTRUCTIVE_INSTALL&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;vendor/bin/install&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-r&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;destructive.dynamic-store-off&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;--no-ansi&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;-vvv&apos;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_YVES_HOST_DE&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;de.&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;SPRYKER_YVES_HOST_AT&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;at.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Original regions section:&lt;/p&gt;
&lt;div class=&quot;language-yaml 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;regions&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;eu-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;eu_search&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Updated regions section:&lt;/p&gt;
&lt;div class=&quot;language-yaml 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;regions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&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_queue&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;de_search&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;session&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;2&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_queue&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;3&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;at_search&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;session&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;4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;Run a normal deploy for your server pipeline.&lt;/li&gt;
&lt;/ol&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;Verification&lt;/div&gt;
- Make sure your store is available at `https://yves.de.mysprykershop.com` or `https://backoffice.de.mysprykershop.com`.
- Make sure the store switcher is *not* displayed on the Storefront.
&lt;/div&gt;&lt;/section&gt;
</description>
            <pubDate>Fri, 19 Jun 2026 12:03:03 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/dynamic-multistore/latest/base-shop/enable-dynamic-multistore.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/dynamic-multistore/latest/base-shop/enable-dynamic-multistore.html</guid>
            
            
        </item>
        
        <item>
            <title>Architecture as Code</title>
            <description>&lt;p&gt;Well-documented project architecture enables faster internal and external onboarding, passes audits cleanly and aligns teams on requirements. Without it, your system becomes a black box that only the original developers understand.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Architecture as Code&lt;/strong&gt; treats architecture documentation like code, similarly to the Infrastructure-as-Code concept. Instead of storing architecture in binary formats or external tools (Word, PowerPoint, Visio, Confluence) that become outdated quickly, you store and maintain architecture documentation in version control alongside your implementation.&lt;/p&gt;
&lt;p&gt;Spryker ships the architecture/ folder with a complete Architecture as Code structure with its demoshop codebase. You can refer to &lt;code&gt;architecture/README.md&lt;/code&gt; that describes how to work with it. Below, you find all the general information about the structure and principles. We go into the details of each aspect to help you understand and extend this architecture documentation in your project.&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;Existing projects&lt;/div&gt;
&lt;p&gt;If your project is based on a Spryker release before 202602.0, refer to the &lt;a href=&quot;https://github.com/spryker-shop/b2b-demo-marketplace&quot;&gt;Spryker B2B Demo Marketplace&lt;/a&gt; master branch source code to see how the Architecture as Code structure is implemented and to copy the “architecture/” folder into your project.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;h2 id=&quot;why-architecture-as-code&quot;&gt;Why Architecture as Code&lt;/h2&gt;
&lt;p&gt;Traditional architecture documentation suffers from several challenges:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Documentation drift&lt;/strong&gt; - Binary formats stored separately from code become outdated quickly&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Version control gaps&lt;/strong&gt; - Specialized tools do not integrate with Git workflows&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collaboration barriers&lt;/strong&gt; - Requires specialized software and binary file merging&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI limitations&lt;/strong&gt; - AI tools understand code and Markdown effectively but struggle with proprietary formats and binary documents&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Architecture as Code solves these problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Version controlled&lt;/strong&gt; - Store in Git alongside implementation. Track every change with full history.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI-ready&lt;/strong&gt; - Markdown and diagrams-as-code enable AI assistance, automated validation, and intelligent analysis.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standard formats&lt;/strong&gt; - Use industry standards (arc42, C4, ADRs, Mermaid) that external teams understand immediately.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collaborative&lt;/strong&gt; - Review through pull requests. No special tools required — only a text editor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Living documentation&lt;/strong&gt; - Update during development, not after. Documentation evolves with your system.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;core-principles&quot;&gt;Core Principles&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Standards over invention&lt;/strong&gt; - Use industry-proven formats and standards (arc42, C4, ADRs, Mermaid) instead of custom documentation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Minimal but sufficient&lt;/strong&gt; - Start simple. Prioritize clarity and expansion over completeness on day one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Architecture is code&lt;/strong&gt; - Write in plain text, store in Git, review through pull requests, deploy automatically.&lt;/p&gt;
&lt;h2 id=&quot;concepts-and-choices&quot;&gt;Concepts and Choices&lt;/h2&gt;
&lt;h3 id=&quot;arc42&quot;&gt;arc42&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://arc42.org/&quot;&gt;arc42&lt;/a&gt; is a proven template for architecture documentation used globally across industries.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why arc42 fits Spryker projects:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Spryker projects vary dramatically in complexity — from simple B2C shops to complex B2B marketplaces with extensive integrations and order management systems. arc42 is flexible enough to cover architecture of any complexity and scales as your Spryker implementation grows. You can start simple with minimal sections and expand as your architecture requires more detail.&lt;/p&gt;
&lt;p&gt;The template includes 12 sections (described below) covering all architectural aspects. Section 4 (Solution Designs) provides RFC-style exploration templates. Section 9 (Architecture Decisions) uses ADRs to document decisions with context and consequences. This workflow — explore with Solution Designs, then document decisions with ADRs — ensures thoughtful architecture evolution.&lt;/p&gt;
&lt;h3 id=&quot;c4-model&quot;&gt;C4 Model&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://c4model.com/&quot;&gt;C4 Model&lt;/a&gt; provides a hierarchical approach to system visualization with four levels of abstraction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why C4 fits Spryker architecture:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Spryker architecture unfolds naturally through C4 layers — start with the system context, zoom into containers (Yves, Zed, Client, databases, services), then dive deeper into layers and components as needed. This progressive detail matches how Spryker complexity reveals itself. The entire Spryker feature set can be shown using this unfolding approach.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Flexibility advantage:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You control the depth. Start at C1 (context) and continue only as deep as your documentation needs require. Given limited architect time, this flexibility is essential — stop at the level appropriate for your stakeholders and complexity.&lt;/p&gt;
&lt;h3 id=&quot;mermaid-and-plantuml&quot;&gt;Mermaid and PlantUML&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Mermaid&lt;/strong&gt; - Our primary choice for diagramming-as-code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Renders automatically in GitHub and GitLab, and can be rendered via plugins in popular IDEs&lt;/li&gt;
&lt;li&gt;No proprietary tools required&lt;/li&gt;
&lt;li&gt;Covers 90% of diagram needs: flowcharts, sequences, C4 diagrams&lt;/li&gt;
&lt;li&gt;Online editor at &lt;a href=&quot;https://mermaid.live/&quot;&gt;mermaid.live&lt;/a&gt; for convenient editing and better visualization than most IDEs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;PlantUML&lt;/strong&gt; - For precision when needed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Essential for Entity-Relationship Diagrams where field-level precision matters&lt;/li&gt;
&lt;li&gt;Online editor at &lt;a href=&quot;https://www.plantuml.com/plantuml/&quot;&gt;plantuml.com&lt;/a&gt; for live editing and preview&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-comes-out-of-the-box&quot;&gt;What Comes Out of the Box&lt;/h2&gt;
&lt;p&gt;We provide templates with clear structure and minimal context, plus examples of the most common diagram types (C4, data flow, integration, sequence) and documentation patterns (ADRs, Solution Designs). AI tools work most efficiently when they see the methodology (like arc42), understand the structure, and have examples to follow. This approach enables you to generate architecture documentation much faster with AI assistance.&lt;/p&gt;
&lt;p&gt;Templates provide examples and structural guidance — you should adapt content to your project’s context. You do not need to implement all sections immediately; remove or keep unused sections based on your documentation strategy.&lt;/p&gt;
&lt;h3 id=&quot;folder-structure&quot;&gt;Folder Structure&lt;/h3&gt;
&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;architecture/
├── 01-introduction-and-goals.md           # Requirements, quality goals, stakeholders
├── 02-constraints.md                      # Technical, organizational constraints
├── 03-system-scope-and-context.md         # System boundaries, external interfaces
│                                          # Includes: External systems and integration tables
├── 04-solution-designs/                   # RFC-style exploration documents
│   ├── README.md                          # Workflow explanation
│   └── sd-000-template.md                 # Solution design template
├── 05-building-block-view.md              # System decomposition
├── 06-runtime-view.md                     # Behavior and interactions
├── 07-deployment-view.md                  # Infrastructure topology
├── 08-crosscutting-concepts.md            # Patterns spanning components
├── 09-architecture-decisions/             # Architecture decision records
│   ├── README.md                          # ADR workflow and sources
│   └── adr-000-template.md                # ADR template
├── 10-quality-requirements.md             # Performance, scalability, testing
│                                          # Includes: Volume planning, testing strategy
├── 11-risks-and-technical-debt.md         # Known issues and mitigation
├── 12-glossary.md                         # Domain terminology
├── diagrams/
│   ├── c4/
│   │   ├── c1-system-context.mmd          # System context diagram
│   │   ├── c2-spryker-container.mmd       # Container diagram
│   │   └── c3-component-diagram.mmd       # Component diagram
│   ├── data-flow/
│   │   └── product-price-data-flow.mmd    # Price data flow example
│   ├── integration/
│   │   └── product-price-integration.mmd  # Integration overview with protocols
│   ├── sequence/
│   │   ├── api-payment.mmd                # Payment flow
│   │   ├── publish-sync.mmd               # Publish and Sync process
│   │   └── punchout-greenwing-integration.mmd  # PunchOut example
│   └── erd/                               # Entity-relationship diagrams (PlantUML)
└── README.md                              # Quick start and overview
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;diagram-organization-and-best-practices&quot;&gt;Diagram Organization and Best Practices&lt;/h3&gt;
&lt;p&gt;You have two approaches to managing diagrams. Choose based on your documentation use case.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Approach 1: Inline Diagrams&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Write diagram code directly in markdown using code fences:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;&gt;flowchart LR
    A --&amp;gt; B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Diagrams are visible immediately; self-contained&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Increases file size; duplicates code if reused&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Approach 2: External Diagram Files&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Store diagram code in &lt;code&gt;/diagrams/&lt;/code&gt; folder and reference via links:&lt;/p&gt;
&lt;div class=&quot;language-markdown 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;nv&quot;&gt;C1 System Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;](&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;diagrams/c4/c1-system-context.mmd&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;Pros:&lt;/strong&gt; Single source-of-truth; cleaner markdown; scalable&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Requires clicking to view (less immediate)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Choosing Your Approach&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This template uses &lt;strong&gt;Approach 2&lt;/strong&gt; for scalability and maintainability, but projects can mix both — use external files for core views, inline for one-off diagrams.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Universal Color Scheme:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;All diagrams use colors optimized for both light and dark modes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Orange &lt;code&gt;#E67E22&lt;/code&gt; - Communication layer&lt;/li&gt;
&lt;li&gt;Blue &lt;code&gt;#2980B9&lt;/code&gt; - Backend services, APIs&lt;/li&gt;
&lt;li&gt;Green &lt;code&gt;#27AE60&lt;/code&gt; - Web apps, external systems&lt;/li&gt;
&lt;li&gt;Purple &lt;code&gt;#9B59B6&lt;/code&gt; - Storage (databases, caches)&lt;/li&gt;
&lt;li&gt;Gray &lt;code&gt;#95A5A6&lt;/code&gt; - Infrastructure&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;templates-and-guidelines&quot;&gt;Templates and Guidelines&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Solution Design Template&lt;/strong&gt; (&lt;code&gt;04-solution-designs/sd-000-template.md&lt;/code&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Metadata section (status, date, stakeholders)&lt;/li&gt;
&lt;li&gt;Problem statement&lt;/li&gt;
&lt;li&gt;Goals and requirements&lt;/li&gt;
&lt;li&gt;Proposed solution with diagrams&lt;/li&gt;
&lt;li&gt;Implementation plan&lt;/li&gt;
&lt;li&gt;Trade-offs and alternatives&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;ADR Template&lt;/strong&gt; (&lt;code&gt;09-architecture-decisions/adr-000-template.md&lt;/code&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Standard ADR structure&lt;/li&gt;
&lt;li&gt;Status tracking&lt;/li&gt;
&lt;li&gt;Context, decision, consequences&lt;/li&gt;
&lt;li&gt;Links to related decisions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Volume Planning Table&lt;/strong&gt; (Section 10):&lt;/p&gt;
&lt;p&gt;Pre-structured table for capacity planning covering:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Catalog entities (products, categories, prices)&lt;/li&gt;
&lt;li&gt;Cart constraints&lt;/li&gt;
&lt;li&gt;User load projections&lt;/li&gt;
&lt;li&gt;B2B customers structure&lt;/li&gt;
&lt;li&gt;Marketplace metrics&lt;/li&gt;
&lt;li&gt;Internationalization requirements&lt;/li&gt;
&lt;li&gt;Orders and infrastructure&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;getting-help&quot;&gt;Getting Help&lt;/h2&gt;
&lt;h3 id=&quot;resources&quot;&gt;Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;arc42 Documentation&lt;/strong&gt;: &lt;a href=&quot;https://arc42.org/&quot;&gt;https://arc42.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C4 Model Guide&lt;/strong&gt;: &lt;a href=&quot;https://c4model.com/&quot;&gt;https://c4model.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mermaid Syntax&lt;/strong&gt;: &lt;a href=&quot;https://mermaid.js.org/&quot;&gt;https://mermaid.js.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ADR Guidelines&lt;/strong&gt;: &lt;a href=&quot;https://adr.github.io/&quot;&gt;https://adr.github.io/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;common-questions&quot;&gt;Common Questions&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Q: Do I need to fill in every arc42 section?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A: No. Fill in what is relevant for your project. Some sections may remain minimal.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q: Can I add custom sections?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A: First, check all 12 arc42 sections — the standard format covers most architectural concerns. If no existing section fits your content, you can add a custom section. Ensure the section purpose is clear and describe it in the README.md file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q: How detailed should diagrams be?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A: For C4 diagrams, follow the levels: Context (high-level), Container (more detail), Component (detailed). For other diagram types (data flow, integration, sequence), follow industry standards for that specific notation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q: When should I create an ADR vs Solution Design?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A: Use Solution Design for exploration. Use ADR for documenting the final decision.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q: Can I use different diagram notations?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A: Yes. The choice is yours — we do not limit you to Mermaid or PlantUML. Ensure your notation follows the core principles: viewable by people, understandable by AI tools, and ideally diagrams-as-code (text-based format in version control).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q: Will diagrams-as-code replace whiteboards and visual editing tools?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A: No. The art of creating and drawing diagrams — whether on whiteboards, visual tools, or pen and paper — remains valuable. Diagrams-as-code is for documenting what you have drawn and decided. Use whatever tools help you think and collaborate, then capture the final result as code for version control and documentation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q: Can I use images as diagrams instead of diagrams-as-code?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A: Yes, it is better to have image diagrams than no diagrams at all, but understand the trade-off. Hand-drawn or visually edited diagrams often look more polished than generated ones. However, images are not as understandable or generable by AI (at least currently) and lack version control benefits. You can use both: include beautiful images for presentations and stakeholder communication, alongside diagrams-as-code for AI assistance and documentation processes.&lt;/p&gt;
</description>
            <pubDate>Fri, 19 Jun 2026 11:56:26 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/architecture/architecture-as-code.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/architecture/architecture-as-code.html</guid>
            
            
        </item>
        
        <item>
            <title>Smart PIM</title>
            <description>Smart PIM is an AI assistant embedded in the Back Office product creation and editing pages. It helps catalog managers enrich product data through AI-powered capabilities — all without leaving the product form.

{% info_block infoBox &quot;Example use case&quot; %}

A catalog manager is adding a new product and needs to write a compelling description, fill in image alt text, and assign the right categories. Instead of doing each task manually, the manager opens the Smart PIM panel and uses the built-in AI tools to generate and refine the content in seconds.

{% endinfo_block %}

## Capabilities

| CAPABILITY | DESCRIPTION |
|------------|-------------|
| Content improvement | Rewrites or enriches product descriptions, names, and attributes using AI. |
| Image alt text | Generates descriptive alt text for product images automatically. |
| Category suggestion | Suggests the most relevant categories for a product based on its content. |
| Translation | Translates product content into other configured languages. |

## Use Smart PIM

1. In the Back Office, go to **Products&amp;nbsp;&lt;span aria-label=&quot;and then&quot;&gt;&gt;&lt;/span&gt;&amp;nbsp;Products** and open a product create or edit page.
2. Click the sparkle icon to open the Smart PIM panel.
   ![Smart PIM sparkle icons](https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-commerce/smart-pim-sparkle-icons.png)
3. Select a capability:
   - **Content improvement or Translation**: Select the option, review the result, and click **Apply**.
     ![Smart PIM translate improve dialog](https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-commerce/smart-pim-translate-improve-dialog.png)
     ![Smart PIM translate dialog result](https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-commerce/smart-pim-translate-dialog-result.png)
   - **Category suggestion**: Review the AI-generated category suggestions and assign them to the product.
     ![Smart PIM category suggestion](https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-commerce/smart-pim-category-suggestion.png)
   - **Image alt text**: Review the AI-generated alt text and apply it to your product images.
     ![Smart PIM image alt text](https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-commerce/smart-pim-image-alt-text.png)
4. Save the product.

## Developer resources

| RESOURCE | DESCRIPTION |
|----------|-------------|
| [Smart PIM](/docs/dg/dev/ai/ai-commerce/smart-pim/smart-pim.html) | Technical overview: architecture, plugin structure, configuration options, and AiFoundation integration. |
| [Install Smart PIM](/docs/dg/dev/ai/ai-commerce/smart-pim/install-smart-pim.html) | Step-by-step installation guide. |
</description>
            <pubDate>Fri, 19 Jun 2026 11:35:00 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/ai-commerce/latest/smart-pim.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/ai-commerce/latest/smart-pim.html</guid>
            
            
        </item>
        
        <item>
            <title>Create a product configurator</title>
            <description>This document explains how to build a custom [product configurator](/docs/pbc/all/product-information-management/{{page.version}}/base-shop/feature-overviews/configurable-product-feature-overview/configurable-product-feature-overview.html) at the code level and integrate it with a Spryker shop.

It uses the *Water Treatment* configurator example (`WaterTreatmentConfiguratorPageExample`) as a reference. The example lets a customer configure an industrial water treatment system (flow rate, filtration type, tank material, control system, inlet connection, and power supply). Use it as a blueprint for your own configurator.

Before you start, make sure the [Product Configuration feature is installed](/docs/pbc/all/product-information-management/{{page.version}}/base-shop/install-and-upgrade/install-features/install-the-product-configuration-feature.html). This document does not cover the core modules (`ProductConfiguration`, `ProductConfigurationStorage`, `ProductConfiguratorGatewayPage`, `ProductConfigurationCart`, the configuration widgets) — they are reused as is.

## Architecture overview

A product configurator consists of two parts:

1. **A standalone configurator application** — a self-contained web app, served on its own host, where the customer selects the configuration. It is not part of the Yves/Zed application; it is bootstrapped from its own `public/` entry point.
2. **Shop-side integration** — the plugins and configuration that let the shop redirect to the configurator, route by configurator key, render the saved configuration on the Storefront and in the Back Office, and accept the configurator key in the cart, shopping list, and checkout.

The data flow between the two is described in [Configuration process flow of configurable products](/docs/pbc/all/product-information-management/{{page.version}}/base-shop/feature-overviews/configurable-product-feature-overview/configuration-process-flow-of-configurable-product.html).

The example module spans the following layers:

```text
src/Pyz/Shared/WaterTreatmentConfiguratorPageExample/      # configurator key constant
src/Pyz/Client/WaterTreatmentConfiguratorPageExample/      # request expander (access token URL)
src/Pyz/Yves/WaterTreatmentConfiguratorPageExample/        # ConfiguratorPage, Theme (HTML shell + render views), render-strategy + security plugins
src/Pyz/Zed/WaterTreatmentConfiguratorPageExample/         # frontend mirror config/console, sales render-strategy plugin
public/WaterTreatmentConfigurator/                         # PHP entry point, configurator.json data, i18n, and the mirrored app assets (dist)
```

## 1. Define the configurator key

Each configurator is identified by a unique key. Add a shared config that exposes it:

```php
// src/Pyz/Shared/WaterTreatmentConfiguratorPageExample/WaterTreatmentConfiguratorPageExampleConfig.php
namespace Pyz\Shared\WaterTreatmentConfiguratorPageExample;

class WaterTreatmentConfiguratorPageExampleConfig
{
    public const WATER_TREATMENT_CONFIGURATOR_KEY = &apos;WATER_TREATMENT_CONFIGURATOR&apos;;
}
```

This key is used everywhere the shop needs to recognize the configurator: the request expander, the supported-keys configs, and the data import.

## 2. Configure the host and shared secrets

The configurator is served on a dedicated host. Set the host and port per environment in your `deploy.*.yml` files, and register the host in the application endpoints:

```yaml
# deploy.dev.yml
environment:
    SPRYKER_WATER_TREATMENT_CONFIGURATOR_HOST: water-treatment-configurator.spryker.local
    SPRYKER_WATER_TREATMENT_CONFIGURATOR_PORT: 80
# ...
groups:
    applications:
        yves:
            endpoints:
                water-treatment-configurator.spryker.local:
                    entry-point: WaterTreatmentConfigurator
```

{% info_block warningBox &quot;Cloud environments&quot; %}

Adding a new host in `deploy.*.yml` does not create the DNS record in the cloud hosted zone. For cloud environments, align with the infrastructure team to add the DNS record for the new host.

{% endinfo_block %}

The configurator and the shop exchange a checksum signed with a shared secret. Keep the encryption key and initialization vector in `config/Shared/config_default.php` (shared by the shop and the configurator app):

```php
$config[ProductConfigurationConstants::SPRYKER_PRODUCT_CONFIGURATOR_ENCRYPTION_KEY] = getenv(&apos;SPRYKER_PRODUCT_CONFIGURATOR_ENCRYPTION_KEY&apos;) ?: &apos;change123&apos;;
$config[ProductConfigurationConstants::SPRYKER_PRODUCT_CONFIGURATOR_HEX_INITIALIZATION_VECTOR] = getenv(&apos;SPRYKER_PRODUCT_CONFIGURATOR_HEX_INITIALIZATION_VECTOR&apos;) ?: &apos;0c1ffefeebdab4a3d839d0e52590c9a2&apos;;
```

The shop redirects the customer to the configurator host, so add the host to the kernel domain allowlist in the same file:

```php
$config[KernelConstants::DOMAIN_WHITELIST][] = getenv(&apos;SPRYKER_WATER_TREATMENT_CONFIGURATOR_HOST&apos;);
```

## 3. Build the standalone configurator application

### Entry point

The configurator app is bootstrapped from its own `public/` entry point. It loads the autoloader only — it does not boot the Yves/Zed kernel—so it stays lightweight:

```php
// public/WaterTreatmentConfigurator/index.php
use Pyz\Yves\WaterTreatmentConfiguratorPageExample\ConfiguratorPage;
use Symfony\Component\HttpFoundation\Response;

define(&apos;APPLICATION&apos;, &apos;CONFIGURATOR&apos;);
defined(&apos;APPLICATION_ROOT_DIR&apos;) || define(&apos;APPLICATION_ROOT_DIR&apos;, dirname(__DIR__, 2));

require_once APPLICATION_ROOT_DIR . &apos;/vendor/autoload.php&apos;;

$response = (new ConfiguratorPage())-&gt;render();

if ($response instanceof Response) {
    $response-&gt;send();
}

echo $response;
```

Because the kernel is not bootstrapped here, read environment values directly with `getenv()` rather than `Config::get()`.

### ConfiguratorPage

`ConfiguratorPage` handles all requests to the configurator host. It distinguishes them by method and query parameters:

- `POST` (create token) — starts a session, stores the request payload sent by the shop, and returns the redirect URL to the configurator page.
- `GET` with `getConfigurationByToken` — returns the stored payload for a session token, so the frontend app can render it.
- `POST` with `prepareConfiguration` — signs the submitted configuration with the shared key and returns the checksum and timestamp.
- Default `GET` — serves the configurator HTML page (the built frontend app).

```php
// src/Pyz/Yves/WaterTreatmentConfiguratorPageExample/ConfiguratorPage.php
namespace Pyz\Yves\WaterTreatmentConfiguratorPageExample;

class ConfiguratorPage
{
    public function render()
    {
        if ($this-&gt;request-&gt;isMethod(&apos;GET&apos;) &amp;&amp; $this-&gt;request-&gt;query-&gt;has(&apos;getConfigurationByToken&apos;)) {
            return $this-&gt;getRequestDataByTokenAction();
        }

        if ($this-&gt;request-&gt;isMethod(&apos;POST&apos;) &amp;&amp; $this-&gt;request-&gt;query-&gt;has(&apos;prepareConfiguration&apos;)) {
            return $this-&gt;prepareConfigurationResponseAction();
        }

        if ($this-&gt;request-&gt;isMethod(&apos;POST&apos;)) {
            return $this-&gt;createTokenAction();
        }

        return $this-&gt;renderHtmlPageAction();
    }

    protected function createConfiguratorRedirectUrl(): string
    {
        return sprintf(
            &apos;%s://%s?token=%s&apos;,
            getenv(&apos;SPRYKER_WATER_TREATMENT_CONFIGURATOR_PORT&apos;) === &apos;443&apos; ? &apos;https&apos; : &apos;http&apos;,
            getenv(&apos;SPRYKER_WATER_TREATMENT_CONFIGURATOR_HOST&apos;) ?: &apos;&apos;,
            htmlspecialchars($this-&gt;session-&gt;getId()),
        );
    }
}
```

The checksum is generated with the shared encryption key and initialization vector, so the shop can validate the configuration the configurator returns:

```php
$checkSum = (new CrcOpenSslChecksumGenerator(getenv(&apos;SPRYKER_PRODUCT_CONFIGURATOR_HEX_INITIALIZATION_VECTOR&apos;) ?: &apos;&apos;))
    -&gt;generateChecksum($productConfiguration, getenv(&apos;SPRYKER_PRODUCT_CONFIGURATOR_ENCRYPTION_KEY&apos;) ?: &apos;&apos;);
```

### Frontend application

The configurator UI is a self-contained frontend application (in the example, an Angular app). The example does not build it at the project level. Instead, it reuses the pre-built, data-driven app shipped in the `spryker-shop/date-time-configurator-page-example` package and feeds it the project&apos;s own data, so no project-level frontend build or tooling is required.

The app is generic: at runtime, it loads its options from `./configurator.json`, served from `public/WaterTreatmentConfigurator/`. To turn it into a water treatment configurator, the example provides its own `public/WaterTreatmentConfigurator/configurator.json` and translations under `public/WaterTreatmentConfigurator/i18n/`.

The default `GET` request to the configurator host serves a static shell, `Theme/index.html`, which loads the app assets from `./dist/`:

```php
// ConfiguratorPage::renderHtmlPageAction()
return file_get_contents(__DIR__ . &apos;/Theme/index.html&apos;);
```

The build step copies the pre-built app into the public folder of the configurator host. In the Zed module config, `getFrontendOriginPath()` resolves the built `dist` inside the vendor package, and `getFrontendTargetPath()` resolves the public folder of the configurator host:

```php
// src/Pyz/Zed/WaterTreatmentConfiguratorPageExample/WaterTreatmentConfiguratorPageExampleConfig.php
protected const FRONTEND_TARGET_PATH = &apos;/public/WaterTreatmentConfigurator/dist&apos;;
protected const FRONTEND_ORIGIN_PATH = &apos;../../../../vendor/spryker-shop/date-time-configurator-page-example/src/SprykerShop/Configurator/DateTimeConfiguratorPageExample/Theme/ConfiguratorApplication/dist&apos;;

public function getFrontendOriginPath(): string
{
    return sprintf(&apos;%s/%s&apos;, __DIR__, static::FRONTEND_ORIGIN_PATH);
}

public function getFrontendTargetPath(): string
{
    return sprintf(&apos;%s/%s&apos;, APPLICATION_ROOT_DIR, static::FRONTEND_TARGET_PATH);
}
```

The copy is handled by the module&apos;s Zed business layer (`Business/Builder/FrontendBuilder`, which mirrors the origin `dist` into the target with `Filesystem::mirror()`), exposed through `WaterTreatmentConfiguratorPageExampleFacade::buildProductConfigurationFrontend()` and triggered by a console command:

```php
// src/Pyz/Zed/WaterTreatmentConfiguratorPageExample/Communication/Console/WaterTreatmentProductConfiguratorBuildFrontendConsole.php
public const COMMAND_NAME = &apos;frontend:water-treatment-product-configurator:build&apos;;
```

Register the console in `Pyz\Zed\Console\ConsoleDependencyProvider` and run it during the build/install step of your environment:

```bash
console frontend:water-treatment-product-configurator:build
```

Running this command copies the configurator app&apos;s built `dist` folder (from `FRONTEND_ORIGIN_PATH`) into the configurator host&apos;s public folder (`FRONTEND_TARGET_PATH`, `public/WaterTreatmentConfigurator/dist`). The configurator host serves the app from there, so the configurator only works after this copy step. Run the command on every build or install, and whenever the app assets or `configurator.json` change.

{% info_block infoBox &quot;Shipping your own frontend app&quot; %}

The example reuses the pre-built app from the `spryker-shop/date-time-configurator-page-example` package, so it needs no frontend tooling at the project level. If you ship your own configurator app instead, point `FRONTEND_ORIGIN_PATH` to its built output and exclude the app sources from the project linters and formatters—for example, add their path to `.stylelintignore` and `.prettierignore`.

{% endinfo_block %}

### Configurator data and translations

The reused app is data-driven: the product, its options, prices, and compatibility rules all come from the project-level `configurator.json`, and the wording comes from the project-level glossary files. Both are served from `public/WaterTreatmentConfigurator/` and are what make this instance a *water treatment* configurator.

`public/WaterTreatmentConfigurator/configurator.json` holds the full product data — the sample product and all possible options:

- `data` — the product shown in the configurator: `name`, `image`, `logo`, and `defaultPrice` per currency.
- `configuration` — the list of parameter groups. Each group has an `id`, a `label`, an optional `tooltip`, and a `data` array of selectable options. Each option carries a `value`, a `title`, a `price` per currency, and optional `disabled` rules that switch off incompatible options in other groups.

```json
{
    &quot;data&quot;: {
        &quot;name&quot;: &quot;Industrial Water Treatment System&quot;,
        &quot;image&quot;: &quot;https://spryker.s3.eu-central-1.amazonaws.com/image/industrial-water-treatment-system.webp&quot;,
        &quot;logo&quot;: &quot;./logo.svg&quot;,
        &quot;defaultPrice&quot;: { &quot;EUR&quot;: 1245000, &quot;CHF&quot;: 1189000, &quot;USD&quot;: 1350000 }
    },
    &quot;configuration&quot;: [
        {
            &quot;id&quot;: &quot;flowRate&quot;,
            &quot;label&quot;: &quot;Flow Rate (m³/h)&quot;,
            &quot;tooltip&quot;: &quot;Select the required flow rate&quot;,
            &quot;data&quot;: [
                {
                    &quot;value&quot;: &quot;5&quot;,
                    &quot;title&quot;: &quot;5 m³/h&quot;,
                    &quot;price&quot;: { &quot;EUR&quot;: 0, &quot;CHF&quot;: 0, &quot;USD&quot;: 0 },
                    &quot;disabled&quot;: {
                        &quot;tank&quot;: {
                            &quot;condition&quot;: [&quot;duplex&quot;, &quot;titanium&quot;],
                            &quot;tooltip&quot;: &quot;Not available with Duplex Steel or Titanium tank&quot;
                        }
                    }
                }
            ]
        }
    ]
}
```

The `i18n/` folder localizes the configurator. Each `i18n/&lt;locale&gt;.json` file (for example, `i18n/de_DE.json`) is a flat glossary that maps the English source strings used in `configurator.json`—group labels, tooltips, and option titles—to their translations. These keys extend the app&apos;s default glossary at the project level, so you add only the strings your own data introduces:

```json
{
    &quot;Flow Rate (m³/h)&quot;: &quot;Durchflussrate (m³/h)&quot;,
    &quot;Filtration Type&quot;: &quot;Filtrationstyp&quot;,
    &quot;Reverse Osmosis&quot;: &quot;Umkehrosmose&quot;,
    &quot;Select the required flow rate&quot;: &quot;Benötigte Durchflussrate auswählen&quot;
}
```

## 4. Route the shop to the configurator

When a customer clicks **Configure**, the shop sends an access-token request to the configurator host. Tell the shop which host to use for your configurator key with a `ProductConfiguratorRequestExpanderPlugin`:

```php
// src/Pyz/Client/WaterTreatmentConfiguratorPageExample/Plugin/ProductConfiguration/ExampleWaterTreatmentProductConfiguratorRequestExpanderPlugin.php
namespace Pyz\Client\WaterTreatmentConfiguratorPageExample\Plugin\ProductConfiguration;

class ExampleWaterTreatmentProductConfiguratorRequestExpanderPlugin extends AbstractPlugin implements ProductConfiguratorRequestExpanderPluginInterface
{
    public function expand(ProductConfiguratorRequestTransfer $productConfiguratorRequestTransfer): ProductConfiguratorRequestTransfer
    {
        return $productConfiguratorRequestTransfer-&gt;setAccessTokenRequestUrl($this-&gt;createConfiguratorUrl());
    }

    protected function createConfiguratorUrl(): string
    {
        return sprintf(
            &apos;%s://%s&apos;,
            getenv(&apos;SPRYKER_WATER_TREATMENT_CONFIGURATOR_PORT&apos;) === &apos;443&apos; ? &apos;https&apos; : &apos;http&apos;,
            getenv(&apos;SPRYKER_WATER_TREATMENT_CONFIGURATOR_HOST&apos;) ?: &apos;&apos;,
        );
    }
}
```

Register it in `Pyz\Client\ProductConfiguration\ProductConfigurationDependencyProvider::getProductConfigurationRequestExpanderPlugins()`.

{% info_block infoBox &quot;Multiple configurators&quot; %}

If your shop has several configurators, route by `configuratorKey` inside `expand()`: read `$productConfiguratorRequestTransfer-&gt;getProductConfiguratorRequestDataOrFail()-&gt;getConfiguratorKey()` and map each key to its host.

{% endinfo_block %}

### Content Security Policy

The shop redirects to the configurator host through a form submit, so the configurator host must be allowlisted in the `form-action` directive of the `Content-Security-Policy` header. Add a `SecurityHeaderExpanderPlugin`:

```php
// src/Pyz/Yves/WaterTreatmentConfiguratorPageExample/Plugin/Application/WaterTreatmentConfiguratorSecurityHeaderExpanderPlugin.php
public function expand(array $securityHeaders): array
{
    $securityHeaders[&apos;Content-Security-Policy&apos;] = str_replace(
        &apos;form-action&apos;,
        sprintf(&apos;form-action %s&apos;, $this-&gt;createConfiguratorUrl()),
        $securityHeaders[&apos;Content-Security-Policy&apos;] ?? &apos;&apos;,
    );

    return $securityHeaders;
}
```

Register it in `Pyz\Yves\Application\ApplicationDependencyProvider`.

## 5. Allow the configurator key in the shop

By default, the configuration widgets and the gateway page accept only the configurator key shipped with the feature. Override `getSupportedConfiguratorKeys()` to add your key in each place a customer can configure or reconfigure a product:

- `Pyz\Yves\ProductConfiguratorGatewayPage\ProductConfiguratorGatewayPageConfig` — the **Configure** button on the Product Details page.
- `Pyz\Yves\ProductConfigurationCartWidget\ProductConfigurationCartWidgetConfig` — reconfiguring from the cart.
- `Pyz\Yves\ProductConfigurationShoppingListWidget\ProductConfigurationShoppingListWidgetConfig` — reconfiguring from a shopping list.

```php
public function getSupportedConfiguratorKeys(): array
{
    return [
        WaterTreatmentConfiguratorPageExampleConfig::WATER_TREATMENT_CONFIGURATOR_KEY,
    ];
}
```

## 6. Render the saved configuration

After a configuration is saved, the shop displays it on the Storefront and in the Back Office. Provide a render-strategy plugin for each location, returning the display data and a template path. Implement one plugin per widget:

| Location | Widget | Plugin interface |
|---|---|---|
| Product Details page | `ProductConfigurationWidget` | `ProductConfigurationRenderStrategyPluginInterface` |
| Cart | `ProductConfigurationCartWidget` | `CartProductConfigurationRenderStrategyPluginInterface` |
| Shopping list | `ProductConfigurationShoppingListWidget` | `ShoppingListItemProductConfigurationRenderStrategyPluginInterface` |
| Order (Storefront) | `SalesProductConfigurationWidget` | `SalesProductConfigurationRenderStrategyPluginInterface` |
| Order (Back Office) | `SalesProductConfigurationGui` | `SalesProductConfigurationRenderStrategyPluginInterface` |

Each Yves plugin implements the widget-specific render-strategy interface (for example, `CartProductConfigurationRenderStrategyPluginInterface` for the cart widget), matches the configurator key in `isApplicable()`, and in `getTemplate()` renders the `options-list` view from the module theme:

```php
public function isApplicable(ProductConfigurationInstanceTransfer $productConfigurationInstance): bool
{
    return $productConfigurationInstance-&gt;getConfiguratorKey() === WaterTreatmentConfiguratorPageExampleConfig::WATER_TREATMENT_CONFIGURATOR_KEY;
}

public function getTemplate(ProductConfigurationInstanceTransfer $productConfigurationInstance): ProductConfigurationTemplateTransfer
{
    return (new ProductConfigurationTemplateTransfer())
        -&gt;setData(json_decode($productConfigurationInstance-&gt;getDisplayDataOrFail(), true) ?? [])
        -&gt;setModuleName(&apos;WaterTreatmentConfiguratorPageExample&apos;)
        -&gt;setTemplateType(&apos;view&apos;)
        -&gt;setTemplateName(&apos;options-list&apos;);
}
```

The Back Office (Zed) plugin renders a presentation partial of the module:

```php
return (new SalesProductConfigurationTemplateTransfer())
    -&gt;setData(json_decode($itemTransfer-&gt;getSalesOrderItemConfigurationOrFail()-&gt;getDisplayDataOrFail(), true) ?? [])
    -&gt;setTemplatePath(&apos;@WaterTreatmentConfiguratorPageExample/_partials/order-item-configuration.twig&apos;);
```

Provide the templates in your module. The Storefront `options-list` view renders the display data as a collapsible list:

{% raw %}

```twig
{# src/Pyz/Yves/WaterTreatmentConfiguratorPageExample/Theme/default/views/options-list/options-list.twig #}
{% extends model(&apos;template&apos;) %}

{% define data = {
    listItems: {},
} %}

{% block body %}
    {% include molecule(&apos;collapsible-list&apos;) with {
        data: {
            listItems: data.listItems,
        },
    } only %}
{% endblock %}
```

{% endraw %}

The Back Office partial renders the first three attributes, with the rest collapsed behind a **Show more** toggle:

{% raw %}

```twig
{# src/Pyz/Zed/WaterTreatmentConfiguratorPageExample/Presentation/_partials/order-item-configuration.twig #}
&lt;br&gt;
{% for key, configuration in data | slice(0, 3) %}
    &lt;div class=&quot;spacing-bottom&quot;&gt;
        &lt;strong&gt;{{ key }}:&lt;/strong&gt; {{ configuration }}
    &lt;/div&gt;
{% endfor %}

{% if data | length &gt; 3 %}
    &lt;div id=&quot;attribute_details_configured-{{ IdSalesOrderItem }}&quot; class=&quot;hidden&quot;&gt;
        {% for key, configuration in data | slice(3) %}
            &lt;div class=&quot;spacing-bottom&quot;&gt;
                &lt;strong&gt;{{ key }}:&lt;/strong&gt; {{ configuration }}
            &lt;/div&gt;
        {% endfor %}
    &lt;/div&gt;

    &lt;a id=&quot;attribute-details-btn-configured-{{ IdSalesOrderItem }}&quot; class=&quot;btn btn-sm more-attributes is-hidden&quot; data-id=&quot;configured-{{ IdSalesOrderItem }}&quot;&gt;
        &lt;span class=&quot;show-more&quot;&gt;{{ &apos;Show more&apos; | trans }}&lt;/span&gt;
        &lt;span class=&quot;show-less&quot;&gt;{{ &apos;Show less&apos; | trans }}&lt;/span&gt;
    &lt;/a&gt;
{% endif %}
```

{% endraw %}

Register each plugin in the corresponding widget&apos;s `DependencyProvider` (for example, `Pyz\Yves\ProductConfigurationCartWidget\ProductConfigurationCartWidgetDependencyProvider`).

## 7. Mark products as configurable

A regular product becomes configurable when a configuration is imported for it. Import a `product_configuration.csv` file that maps the concrete SKU to your configurator key:

```csv
concrete_sku,configurator_key,is_complete,default_configuration,default_display_data
IWT-SYSTEM-1,WATER_TREATMENT_CONFIGURATOR,0,,
```

- `is_complete` — whether the imported configuration is complete. If `0`, the customer must open the configurator and save a configuration before purchasing. See [Complete and incomplete configuration](/docs/pbc/all/product-information-management/{{page.version}}/base-shop/feature-overviews/configurable-product-feature-overview/configurable-product-feature-overview.html#complete-and-incomplete-configuration).
- `default_configuration` / `default_display_data` — optional [preconfigured parameter values](/docs/pbc/all/product-information-management/{{page.version}}/base-shop/feature-overviews/configurable-product-feature-overview/configurable-product-feature-overview.html#preconfigured-parameter-values).

Add the import to your data import configuration:

```yaml
- data_entity: product-configuration
  source: data/import/.../product_configuration.csv
```

## Verify the integration

1. Build the configurator frontend app and clear the cache (`console cache:empty-all`).
2. Import a configurable product.
3. On the Product Details page of the product, click **Configure** — you must be redirected to the configurator host.
4. Select a configuration and submit — you must be redirected back to the shop with the configuration applied.
5. Add the product to the cart, reconfigure it from the cart, and place an order — the configuration must be displayed on the cart, order, and in the Back Office.

## Related documents

- [Configurable Product feature overview](/docs/pbc/all/product-information-management/{{page.version}}/base-shop/feature-overviews/configurable-product-feature-overview/configurable-product-feature-overview.html)
- [Configuration process flow of configurable products](/docs/pbc/all/product-information-management/{{page.version}}/base-shop/feature-overviews/configurable-product-feature-overview/configuration-process-flow-of-configurable-product.html)
- [Install the Product Configuration feature](/docs/pbc/all/product-information-management/{{page.version}}/base-shop/install-and-upgrade/install-features/install-the-product-configuration-feature.html)
</description>
            <pubDate>Tue, 16 Jun 2026 14:58:00 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/product-information-management/latest/base-shop/tutorials-and-howtos/howto-create-a-product-configurator.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/product-information-management/latest/base-shop/tutorials-and-howtos/howto-create-a-product-configurator.html</guid>
            
            
        </item>
        
        <item>
            <title>Configuration process flow of configurable products</title>
            <description>&lt;p&gt;The configuration process of a configurable product consists of eight phases illustrated in the following flow chart:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://confluence-connect.gliffy.net/embed/image/49194e23-84dc-4dc8-8ce2-29fa244522b3.png?utm_medium=live&amp;amp;utm_source=custom&quot; alt=&quot;configuration-flow&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;product-configuration-data-flow-on-the-product-details-page&quot;&gt;Product configuration data flow on the product details page&lt;/h2&gt;
&lt;p&gt;When configuration starts on Yves, from the product details page (PDP), the product configuration data flow consists of the following phases.&lt;/p&gt;
&lt;h3 id=&quot;phase-1&quot;&gt;Phase 1&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A customer is on a PDP—the page with the product configuration.&lt;/li&gt;
&lt;li&gt;The product configuration can be a complete configuration or use an incomplete pre-configuration defined by the shop owner.&lt;/li&gt;
&lt;li&gt;The product configuration can be taken from two sources:
&lt;ul&gt;
&lt;li&gt;A session for complete configuration.&lt;/li&gt;
&lt;li&gt;Key-value store (Redis or Valkey) for the pre-configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following table shows the configuration data that is stored in the Session and Storage.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PARAMETER&lt;/th&gt;
&lt;th&gt;VALUE&lt;/th&gt;
&lt;th&gt;COMMENTS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProductConfigurationInstance.isComplete&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sensitive data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProductConfigurationInstance.displayData&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Some text of JSON blob—for example, &lt;code&gt;{&amp;quot;Flow Rate&amp;quot;:&amp;quot;10 m³/h&amp;quot;,&amp;quot;Filtration Type&amp;quot;:&amp;quot;Reverse Osmosis&amp;quot;,&amp;quot;Tank Material&amp;quot;:&amp;quot;SS316L&amp;quot;}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProductConfigurationInstance.configuratorKey&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;WATER_TREATMENT_CONFIGURATOR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProductConfigurationInstance.configuration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{&amp;quot;flowRate&amp;quot;:&amp;quot;10&amp;quot;,&amp;quot;filtration&amp;quot;:&amp;quot;reverse_osmosis&amp;quot;,&amp;quot;tank&amp;quot;:&amp;quot;ss316l&amp;quot;}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sensitive data.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The framework generates a back URL that points to the gateway page with the following parameters:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PARAMETER&lt;/th&gt;
&lt;th&gt;VALUE&lt;/th&gt;
&lt;th&gt;COMMENT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sourceType&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pdp&lt;/code&gt;, &lt;code&gt;cart-page&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Defines the page type where the configurator request is started. Resolves the &lt;code&gt;backUrl&lt;/code&gt;, when the configurator response is processed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SKU&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;some_sku&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Quantity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The following table contains request parameters, which the plugin adds to the request:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PARAMETER&lt;/th&gt;
&lt;th&gt;VALUE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;idCustomer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DE-1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;localeName&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;de_DE&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;storeName&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DE&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;currencyCode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;EUR&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;priceMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NET_MODE&lt;/code&gt; or &lt;code&gt;GROSS_MODE&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backUrl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://SOME_URL&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;phase-2&quot;&gt;Phase 2&lt;/h3&gt;
&lt;p&gt;The customer clicks the configuration button, and the request is redirected to the gateway page with a given product configuration using a &lt;code&gt;GET&lt;/code&gt; method.&lt;/p&gt;
&lt;h3 id=&quot;phase-3&quot;&gt;Phase 3&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;The configuration data is read from the session or storage for the given SKU.&lt;/li&gt;
&lt;li&gt;A plugin that handles the request is selected based on the configurator type.&lt;/li&gt;
&lt;li&gt;The plugin expands the request with additional necessary data:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;store, locale, currency, customer, price mode.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;backUrl&lt;/code&gt;—based on the referer header.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;The plugin generates the URL that points to the configurator page together with all of the necessary data.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;phase-4&quot;&gt;Phase 4&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;The request comes from the gateway to the configurator &lt;code&gt;index.php&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The configurator handles the request by creating a new session and saving the request’s data there.&lt;/li&gt;
&lt;li&gt;The configurator responds with a redirect URL that contains what session ID needs to be picked up.&lt;/li&gt;
&lt;li&gt;The request comes from the gateway to the configurator &lt;code&gt;index.php&lt;/code&gt; to pick up a session.&lt;/li&gt;
&lt;li&gt;The response redirects the customer to the configurator page by a GET request using a secured connection.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;phase-5&quot;&gt;Phase 5&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;The customer is on the configurator page.&lt;/li&gt;
&lt;li&gt;The configurator page is rendered based on the data in the session.&lt;/li&gt;
&lt;li&gt;The customer does the configuration and submits the configuration form.&lt;/li&gt;
&lt;li&gt;The configurator redirects the customer to the gateway page with configuration data.&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PARAMETER&lt;/th&gt;
&lt;th&gt;VALUE&lt;/th&gt;
&lt;th&gt;COMMENT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProductConfigurationInstance.prices&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{{&amp;quot;EUR&amp;quot;:{&amp;quot;GROSS_MODE&amp;quot;:{&amp;quot;DEFAULT&amp;quot;:30000}},{&amp;quot;NET_MODE&amp;quot;:{&amp;quot;DEFAULT&amp;quot;: 25000}},&amp;quot;priceData&amp;quot;:{&amp;quot;volume_prices&amp;quot;:[{&amp;quot;quantity&amp;quot;: 5,&amp;quot;net_price&amp;quot;: 28500,&amp;quot;gross_price&amp;quot;: 29000}]}}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sensitive data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProductConfigurationInstance.isComplete&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sensitive data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProductConfigurationInstance.availableQuantity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProductConfigurationInstance.displayData&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Some text of JSON blob—for example, &lt;code&gt;{&amp;quot;Flow Rate&amp;quot;:&amp;quot;10 m³/h&amp;quot;,&amp;quot;Filtration Type&amp;quot;:&amp;quot;Reverse Osmosis&amp;quot;,&amp;quot;Tank Material&amp;quot;:&amp;quot;SS316L&amp;quot;}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProductConfigurationInstance.configuration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{&amp;quot;flowRate&amp;quot;:&amp;quot;10&amp;quot;,&amp;quot;filtration&amp;quot;:&amp;quot;reverse_osmosis&amp;quot;,&amp;quot;tank&amp;quot;:&amp;quot;ss316l&amp;quot;}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sensitive data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;idCustomer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DE-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sourceType&lt;/td&gt;
&lt;td&gt;SOURCE_TYPE_PDP, SOURCE_TYPE_CART, SOURCE_TYPE_WISHLIST_DETAIL, …&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SKU&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;some_sku&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;timestamp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1231313123123&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CheckSum&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;It’s an encrypted value of the &lt;code&gt;CheckSum&lt;/code&gt;. It must be based on the all requested parameters and must have the same order for decryption.&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;phase-6&quot;&gt;Phase 6&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;The customer finishes the configuration and clicks a button that creates an AJAX POST request with the data to the configurator page (self).&lt;/li&gt;
&lt;li&gt;In the backend, the response is prepared according to the public data API from the configurator to Spryker.&lt;/li&gt;
&lt;li&gt;In the backend, a &lt;code&gt;CheckSum&lt;/code&gt; is prepared based on the response data, which is encrypted with a shared key and returns these as the AJAX response.&lt;/li&gt;
&lt;li&gt;On the configurator page, the framework puts data to a hidden form and submits the form, which points to the gateway page.&lt;/li&gt;
&lt;li&gt;After the successful configuration, the customer is redirected to the configurator gateway with a configuration response.&lt;/li&gt;
&lt;li&gt;The gateway URL does not equal the back URL; it’s a fixed, known URL.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;phase-7&quot;&gt;Phase 7&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;The gateway page receives data.&lt;/li&gt;
&lt;li&gt;The data is checked through the execution of the validator plugins stack for received data.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;If validation is not successful, the request is redirected to the &lt;code&gt;backUrl&lt;/code&gt; without saving the configuration. A warning message is displayed.&lt;/li&gt;
&lt;li&gt;If the validation part is successful, the configuration is saved to the session.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;All applicable plugins that can handle the configurator response are executed. A plugin that applies to the PDP source type resolves the back URL according to the response data.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;phase-8&quot;&gt;Phase 8&lt;/h3&gt;
&lt;p&gt;The customer is redirected back to the PDP.&lt;/p&gt;
&lt;h2 id=&quot;product-configuration-data-flow-on-the-cart-page&quot;&gt;Product configuration data flow on the Cart page&lt;/h2&gt;
&lt;p&gt;When configuration starts on Yves, from the &lt;strong&gt;Cart&lt;/strong&gt; page, the product configuration data flow consists of the following phases:&lt;/p&gt;
&lt;h3 id=&quot;phase-1-1&quot;&gt;Phase 1&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The customer is on the &lt;strong&gt;Cart&lt;/strong&gt; page—the page with items that contains the product configuration.&lt;/li&gt;
&lt;li&gt;The product configuration can be already complete or not.&lt;/li&gt;
&lt;li&gt;The item product configuration can be taken from one source only: Quote.&lt;/li&gt;
&lt;li&gt;The framework generates the URL that points to the gateway page with the following parameters.&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PARAMETER&lt;/th&gt;
&lt;th&gt;VALUE&lt;/th&gt;
&lt;th&gt;COMMENT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProductConfigurationInstance.displayData&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Some text of JSON blob—for example, &lt;code&gt;{&amp;quot;Flow Rate&amp;quot;:&amp;quot;10 m³/h&amp;quot;,&amp;quot;Filtration Type&amp;quot;:&amp;quot;Reverse Osmosis&amp;quot;,&amp;quot;Tank Material&amp;quot;:&amp;quot;SS316L&amp;quot;}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProductConfigurationInstance.configuration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{&amp;quot;flowRate&amp;quot;:&amp;quot;10&amp;quot;,&amp;quot;filtration&amp;quot;:&amp;quot;reverse_osmosis&amp;quot;,&amp;quot;tank&amp;quot;:&amp;quot;ss316l&amp;quot;}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sensitive data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProductConfigurationInstance.configuratorKey&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;WATER_TREATMENT_CONFIGURATOR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProductConfigurationInstance.isComplete&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sensitive data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backUrl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://some.url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sensitive data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SubmitUrl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://some.url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sensitive data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sourceType&lt;/td&gt;
&lt;td&gt;SOURCE_TYPE_PDP, SOURCE_TYPE_CART, SOURCE_TYPE_WISHLIST_DETAIL, …&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SKU&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;some_sku&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Quantity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ItemGroupKey&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;some_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;phase-2-1&quot;&gt;Phase 2&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;The customer clicks the configuration button.&lt;/li&gt;
&lt;li&gt;The form request is redirected to the gateway page with a given product configuration using the &lt;code&gt;GET&lt;/code&gt; method.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;phase-3-1&quot;&gt;Phase 3&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;The configuration data is read from the quote for the given &lt;code&gt;ItemGroupKey&lt;/code&gt; and &lt;code&gt;SKU&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A plugin that handles the request is selected based on the configurator type.&lt;/li&gt;
&lt;li&gt;The plugin expands the request with additional necessary data:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;store, locale, currency, customer, price mode.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CheckSum&lt;/code&gt;—calculates the CRC32 polynomial of a request data as a string.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;timestamp&lt;/code&gt;—the timestamp when the request is created.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;backUrl&lt;/code&gt;—based on the &lt;code&gt;Referer&lt;/code&gt; header.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;The plugin generates the URL that points to the configurator page together with all the necessary data.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;phase-4-1&quot;&gt;Phase 4&lt;/h3&gt;
&lt;p&gt;Redirects the customer to the configurator page using the GET request.&lt;/p&gt;
&lt;h3 id=&quot;phase-5-1&quot;&gt;Phase 5&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;The customer is on the configurator page.&lt;/li&gt;
&lt;li&gt;The customer does the configuration.&lt;/li&gt;
&lt;li&gt;Submits the configuration form.&lt;/li&gt;
&lt;li&gt;The configurator has to redirect to the gateway page with configuration data.&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PARAMETER&lt;/th&gt;
&lt;th&gt;VALUE&lt;/th&gt;
&lt;th&gt;COMMENT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProductConfigurationInstance.prices&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{&amp;quot;EUR&amp;quot;:{&amp;quot;GROSS_MODE&amp;quot;:{&amp;quot;DEFAULT&amp;quot;:30000}},{&amp;quot;NET_MODE&amp;quot;:{&amp;quot;DEFAULT&amp;quot;: 25000}},&amp;quot;priceData&amp;quot;:{&amp;quot;volume_prices&amp;quot;:[{&amp;quot;quantity&amp;quot;: 5,&amp;quot;net_price&amp;quot;: 28500,&amp;quot;gross_price&amp;quot;: 29000}]}}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sensitive data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProductConfigurationInstance.isComplete&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sensitive data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProductConfigurationInstance.availableQuantity&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProductConfigurationInstance.displayData&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Some text of JSON blob—for example, &lt;code&gt;{&amp;quot;Flow Rate&amp;quot;:&amp;quot;10 m³/h&amp;quot;,&amp;quot;Filtration Type&amp;quot;:&amp;quot;Reverse Osmosis&amp;quot;,&amp;quot;Tank Material&amp;quot;:&amp;quot;SS316L&amp;quot;}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProductConfigurationInstance.configuration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{&amp;quot;flowRate&amp;quot;:&amp;quot;10&amp;quot;,&amp;quot;filtration&amp;quot;:&amp;quot;reverse_osmosis&amp;quot;,&amp;quot;tank&amp;quot;:&amp;quot;ss316l&amp;quot;}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sensitive data.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ProductConfigurationInstance.timestamp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10312313135234&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Sensitive data, a certain configuration must be valid only a certain amount of the time given.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;sourceType&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SOURCE_TYPE_PDP&lt;/code&gt;, &lt;code&gt;SOURCE_TYPE_CART&lt;/code&gt;, &lt;code&gt;SOURCE_TYPE_WISHLIST_DETAIL&lt;/code&gt;, …&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SKU&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;some_sku&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;itemGroupKey&lt;/td&gt;
&lt;td&gt;&lt;code&gt;some_group_key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;phase-6-1&quot;&gt;Phase 6&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;After the successful configuration, the customer is redirected to the configurator gateway with a configuration response.&lt;/li&gt;
&lt;li&gt;The gateway URL is not the same as the back URL; it’s a fixed known URL.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;phase-7-1&quot;&gt;Phase 7&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;The gateway page receives data.&lt;/li&gt;
&lt;li&gt;The data is checked through the execution of the validator plugins stack for received data.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;If validation is not successful, the request is redirected to &lt;code&gt;backUrl&lt;/code&gt; without saving the configuration. A warning message is displayed.&lt;/li&gt;
&lt;li&gt;If validation is successful, the framework updates the cart item configuration in the cart.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;All applicable plugins that can handle the configurator response are executed. A plugin that applies to the cart page source type resolves the back URL according to the response data.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;phase-8-1&quot;&gt;Phase 8&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;The customer is redirected back to the &lt;strong&gt;Cart&lt;/strong&gt; page.&lt;/li&gt;
&lt;/ol&gt;
</description>
            <pubDate>Tue, 16 Jun 2026 14:58:00 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/product-information-management/latest/base-shop/feature-overviews/configurable-product-feature-overview/configuration-process-flow-of-configurable-product.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/product-information-management/latest/base-shop/feature-overviews/configurable-product-feature-overview/configuration-process-flow-of-configurable-product.html</guid>
            
            
        </item>
        
        <item>
            <title>Configurable Product feature overview</title>
            <description>&lt;p&gt;The &lt;em&gt;Configurable Product&lt;/em&gt; feature introduces a new type of product, a configurable product, that customers can customize.&lt;/p&gt;
&lt;p&gt;The feature lets you sell complex products with modular designs or services. For example, if you sell clothes, your customers can define the material and color and add their names to the product. Or if you are selling a service, you can allow them to select a preferred date and time for the service delivery.&lt;/p&gt;
&lt;h2 id=&quot;configurable-product&quot;&gt;Configurable product&lt;/h2&gt;
&lt;p&gt;A &lt;em&gt;configurable product&lt;/em&gt; is a product that customers can customize based on the parameters provided in a &lt;a href=&quot;#product-configurator&quot;&gt;product configurator&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For example, if you are selling an industrial water treatment system, before purchasing it, customers can configure parameters such as the flow rate, filtration type, and tank material.&lt;/p&gt;
&lt;h3 id=&quot;configuring-a-configurable-product&quot;&gt;Configuring a configurable product&lt;/h3&gt;
&lt;p&gt;To configure a product, a customer opens a product configurator from the &lt;em&gt;Product Details&lt;/em&gt; page by clicking the &lt;strong&gt;Configure&lt;/strong&gt; button. They are then redirected back to the &lt;em&gt;Product Details&lt;/em&gt; page and can add the configured product to the wishlist or cart.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/scos/user/features/configurable-product-feature-overview/configure-button-on-product-details-page.png&quot; alt=&quot;configure-button-on-product-details-page&quot; /&gt;&lt;/p&gt;
&lt;p&gt;After adding a configurable product to the cart, a customer can change the product configuration from the &lt;strong&gt;Cart&lt;/strong&gt; page.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/scos/user/features/configurable-product-feature-overview/configure-button-on-the-cart-page.png&quot; alt=&quot;configure-button-on-the-cart-page&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;creating-configurable-products&quot;&gt;Creating configurable products&lt;/h3&gt;
&lt;p&gt;Configurable products are created in two steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A Back Office user creates regular products, or a developer imports them. See &lt;a href=&quot;/docs/pbc/all/product-information-management/latest/base-shop/manage-in-the-back-office/products/manage-abstract-products-and-product-bundles/create-abstract-products-and-product-bundles.html&quot;&gt;Creating an abstract product&lt;/a&gt; to learn how they create products in the Back Office or &lt;a href=&quot;/docs/pbc/all/product-information-management/latest/base-shop/import-and-export-data/products-data-import/import-file-details-product-concrete.csv.html&quot;&gt;File details: product_concrete.csv&lt;/a&gt; to learn about the file they import.&lt;/li&gt;
&lt;li&gt;A developer converts regular products into configurable products by importing configuration parameters. See &lt;a href=&quot;/docs/pbc/all/product-information-management/latest/base-shop/import-and-export-data/import-file-details-product-concrete-pre-configuration.csv.html&quot;&gt;File details: product_concrete_pre_configuration.csv&lt;/a&gt; to learn about the file they import.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;managing-configurable-products&quot;&gt;Managing configurable products&lt;/h3&gt;
&lt;p&gt;A Back Office user can add configurable products as regular products to pages, categories, and content items.&lt;/p&gt;
&lt;p&gt;They can see which products are configurable in the product catalog and edit them as regular products. However, a Back Office user cannot change configuration parameters.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/scos/user/features/configurable-product-feature-overview/configurable-product-entry-in-the-back-office.png&quot; alt=&quot;configurable-product-entry-in-the-back-office&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In the orders, a Back Office user can see which products are configurable. They can also see the configuration of each product, but they cannot change the selected parameters.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/scos/user/features/configurable-product-feature-overview/order-with-a-configurable-product.png&quot; alt=&quot;order-with-a-configurable-product&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;product-configurator&quot;&gt;Product configurator&lt;/h2&gt;
&lt;p&gt;A &lt;em&gt;product configurator&lt;/em&gt; is a tool that lets customers customize the product parameters provided by the shop owner or product manufacturer.&lt;/p&gt;
&lt;p&gt;You can create a product configurator as a part of your shop or integrate a third-party one. The feature is shipped with an example product configurator. The example product configurator allows configuring the parameters of an industrial water treatment system, such as &lt;em&gt;Flow Rate&lt;/em&gt;, &lt;em&gt;Filtration Type&lt;/em&gt;, &lt;em&gt;Tank Material&lt;/em&gt;, &lt;em&gt;Control System&lt;/em&gt;, &lt;em&gt;Inlet Connection&lt;/em&gt;, and &lt;em&gt;Power Supply&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/scos/user/features/configurable-product-feature-overview/examplary-product-configurator.png&quot; alt=&quot;examplary-product-configurator&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;how-a-spryker-shop-interacts-with-a-product-configurator&quot;&gt;How a Spryker shop interacts with a product configurator&lt;/h3&gt;
&lt;p&gt;A Spryker shop interacts with product configurators using parameters. When a customer is redirected from a Spryker shop to the configurator page, the shop passes the customer and product parameters. When the customer is redirected back to the shop, the configurator passes the updated parameters back.&lt;/p&gt;
&lt;p&gt;The behavior of the configurator is based on the parameters passed by a shop. For example, a shop passes the &lt;code&gt;store_name&lt;/code&gt; parameter. If a customer is redirected from a US store, the language of the configurator is English. Also, the shop passes the URL of the page the customer is redirected from. After the customer saves the configuration, the configurator uses this URL to redirect them back to the same page.&lt;/p&gt;
&lt;p&gt;The selected configuration is also passed back to the shop as parameters. The shop uses the parameters to display the selected configuration on all related pages and process the order with the product.&lt;/p&gt;
&lt;h3 id=&quot;parameter-types&quot;&gt;Parameter types&lt;/h3&gt;
&lt;p&gt;There are two parameter types: configuration parameters and display parameters.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Configuration parameters&lt;/em&gt; are used by shops to interact with product configurators.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Display parameters&lt;/em&gt; are used to display product configuration on the Storefront and in the Back Office.&lt;/p&gt;
&lt;p&gt;Display parameter values are usually converted from configuration parameter values to show data in a user-friendly format. For example, a product configurator passes the configuration parameter to a shop: &lt;code&gt;&amp;quot;filtration&amp;quot;: &amp;quot;reverse_osmosis&amp;quot;&lt;/code&gt;. Since, in the configurator, &lt;code&gt;reverse_osmosis&lt;/code&gt; stands for the human-readable option &lt;code&gt;Reverse Osmosis&lt;/code&gt;, the shop displays &lt;strong&gt;Filtration Type: Reverse Osmosis&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/scos/user/features/configurable-product-feature-overview/display-data-in-a-configurator.png&quot; alt=&quot;display-data-in-a-configurator&quot; /&gt;&lt;/p&gt;
&lt;h3 id=&quot;availability-calculation-in-a-product-configurator&quot;&gt;Availability calculation in a product configurator&lt;/h3&gt;
&lt;p&gt;The availability of a configurable product is based on the selected configuration.&lt;/p&gt;
&lt;p&gt;A customer selects the quantity of a product in a configurator or a shop. If a configurator allows them to select a product quantity, it passes the specified quantity to the shop as a parameter. Otherwise, it passes the availability as a parameter, and they select the product quantity in the shop.&lt;/p&gt;
&lt;p&gt;If a configurator does not pass availability, &lt;a href=&quot;/docs/pbc/all/warehouse-management-system/latest/marketplace/install-features/install-the-marketplace-inventory-management-feature.html&quot;&gt;regular product availability&lt;/a&gt; is used.&lt;/p&gt;
&lt;h3 id=&quot;price-calculation-in-a-product-configurator&quot;&gt;Price calculation in a product configurator&lt;/h3&gt;
&lt;p&gt;The price of a configurable product is based on the selected configuration. When a customer chooses a configuration, the product’s price in the selected configuration is displayed. After they save the configuration, the product price in the selected configuration is passed to the shop. The customer is redirected back to the shop where they can purchase the product for the price.&lt;/p&gt;
&lt;p&gt;If the configurator does not provide a price, &lt;a href=&quot;/docs/pbc/all/price-management/latest/base-shop/prices-feature-overview/prices-feature-overview.html&quot;&gt;a regular product price&lt;/a&gt; is used.&lt;/p&gt;
&lt;h3 id=&quot;complete-and-incomplete-configuration&quot;&gt;Complete and incomplete configuration&lt;/h3&gt;
&lt;p&gt;When &lt;a href=&quot;/docs/pbc/all/product-information-management/latest/base-shop/import-and-export-data/import-file-details-product-concrete-pre-configuration.csv.html&quot;&gt;importing configurable products&lt;/a&gt;, a developer defines if the configuration is complete for each product.&lt;/p&gt;
&lt;p&gt;If the configuration is complete, on the &lt;em&gt;Product details&lt;/em&gt; page, a customer sees a message that the configuration is complete. By default, the message is followed by the first three descriptive attributes set in the configurator. Under the attributes, the &lt;strong&gt;Show&lt;/strong&gt; and &lt;strong&gt;Hide&lt;/strong&gt; buttons let the customer expand and collapse the remaining attributes to review them. The customer can purchase the product without again opening the configurator and selecting parameters, if they determine the configuration is complete.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/scos/user/features/configurable-product-feature-overview/configurtion-complete-message.png&quot; alt=&quot;configurtion-complete-message&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If the configuration is not complete, on the &lt;em&gt;Product details&lt;/em&gt; page, a customer sees a message that the configuration needs to be completed. To purchase the product, they open the configurator and select a configuration. However, they can add a product with an incomplete configuration to a wishlist. In this scenario, they can finish the configuration from the &lt;em&gt;Wishlist&lt;/em&gt; page.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/scos/user/features/configurable-product-feature-overview/incomplete-configurtion-message.png&quot; alt=&quot;incomplete-configurtion-message&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Even if all the parameter values are &lt;a href=&quot;#preconfigured-parameter-values&quot;&gt;preconfigured&lt;/a&gt;, but the configuration is not complete, a customer has to open the configurator and save the configuration. They are not required to change the preconfigured values, though.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/scos/user/features/configurable-product-feature-overview/configuration-is-not-complete-message-with-pre-configured-parameters.png&quot; alt=&quot;configuration-is-not-complete-message-with-preconfigured-parameters&quot; /&gt;&lt;/p&gt;
&lt;h4 id=&quot;request-for-quote-with-a-configurable-product&quot;&gt;Request for Quote with a configurable product&lt;/h4&gt;
&lt;p&gt;The information in the &lt;a href=&quot;#complete-and-incomplete-configuration&quot;&gt;Complete and incomplete configuration&lt;/a&gt; section applies to &lt;a href=&quot;/docs/pbc/all/request-for-quote/latest/request-for-quote.html&quot;&gt;Quotation Process &amp;amp; RFQ&lt;/a&gt; functionalities. A customer can only request a quote for a product with a complete configuration.&lt;/p&gt;
&lt;h4 id=&quot;shopping-list-with-a-configurable-product&quot;&gt;Shopping List with a configurable product&lt;/h4&gt;
&lt;p&gt;The information in the &lt;a href=&quot;#complete-and-incomplete-configuration&quot;&gt;Complete and incomplete configuration&lt;/a&gt; section applies to the &lt;a href=&quot;/docs/pbc/all/shopping-list-and-wishlist/latest/base-shop/shopping-lists-feature-overview/shopping-lists-feature-overview.html&quot;&gt;Shopping List&lt;/a&gt; functionality. A customer can add products with the complete or incomplete configuration.&lt;/p&gt;
&lt;h4 id=&quot;wish-list-with-a-configurable-product&quot;&gt;Wish List with a configurable product&lt;/h4&gt;
&lt;p&gt;The information in the &lt;a href=&quot;#complete-and-incomplete-configuration&quot;&gt;Complete and incomplete configuration&lt;/a&gt; section applies to the &lt;a href=&quot;/docs/pbc/all/shopping-list-and-wishlist/latest/base-shop/wishlist-feature-overview.html&quot;&gt;Wish List&lt;/a&gt; functionality. A customer can add products with the complete or incomplete configuration.&lt;/p&gt;
&lt;h3 id=&quot;preconfigured-parameter-values&quot;&gt;Preconfigured parameter values&lt;/h3&gt;
&lt;p&gt;When a developer creates configurable products by importing them, they can preconfigure parameter values. If customers choose to configure such a product, they start with the preconfigured parameter values and can change them.&lt;/p&gt;
&lt;p&gt;If a developer also defines that the configuration of such a product is complete, a customer sees the preconfigured parameter values on the &lt;em&gt;Product details&lt;/em&gt; page. They can add the product to the cart without adjusting the configuration.&lt;/p&gt;
&lt;p&gt;If a developer defines that the configuration of such a product needs to be completed, on the &lt;em&gt;Product details&lt;/em&gt; page a customer does not see the preconfigured parameter values. However, they are still assigned to the product. The customer has to configure the product, but they can keep the same preconfigured parameter values.&lt;/p&gt;
&lt;h2 id=&quot;configurable-product-on-the-storefront&quot;&gt;Configurable product on the Storefront&lt;/h2&gt;
&lt;p&gt;Customers configure a product on the Storefront as follows:&lt;/p&gt;
&lt;iframe width=&quot;960&quot; height=&quot;720&quot; src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/scos/user/features/configurable-product-feature-overview/configurable-product-on-the-storefront.mp4&quot; frameborder=&quot;0&quot; allowfullscreen&gt;&lt;/iframe&gt;
&lt;h2 id=&quot;related-developer-documents&quot;&gt;Related Developer documents&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;INSTALLATION GUIDES&lt;/th&gt;
&lt;th&gt;MIGRATION GUIDES&lt;/th&gt;
&lt;th&gt;DATA IMPORT&lt;/th&gt;
&lt;th&gt;REFERENCES&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/docs/pbc/all/product-information-management/latest/base-shop/install-and-upgrade/install-features/install-the-product-feature.html&quot;&gt;Install the Product feature&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/docs/pbc/all/product-information-management/latest/base-shop/install-and-upgrade/upgrade-modules/upgrade-the-productconfiguration-module.html&quot;&gt;Upgrade the ProductConfiguration module&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/docs/pbc/all/product-information-management/latest/base-shop/import-and-export-data/import-file-details-product-concrete-pre-configuration.csv.html&quot;&gt;File details product_concrete_pre_configuration.csv&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/docs/pbc/all/product-information-management/latest/base-shop/feature-overviews/configurable-product-feature-overview/configuration-process-flow-of-configurable-product.html&quot;&gt;Configuration process flow of Configurable Product&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/docs/pbc/all/product-information-management/latest/base-shop/install-and-upgrade/install-glue-api/install-the-product-configuration-glue-api.html&quot;&gt;Install the Product Configuration Glue API&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/docs/pbc/all/product-information-management/latest/base-shop/install-and-upgrade/upgrade-modules/upgrade-the-productconfigurationstorage-module.html&quot;&gt;Upgrade the ProductConfigurationStorage module&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/docs/pbc/all/product-information-management/latest/base-shop/tutorials-and-howtos/howto-create-a-product-configurator.html&quot;&gt;Create a product configurator&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/docs/pbc/all/product-information-management/latest/base-shop/install-and-upgrade/upgrade-modules/upgrade-the-productconfigurationspriceproductvolumesrestapi-module.html&quot;&gt;Upgrade the ProductConfigurationsPriceProductVolumesRestApi module&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/docs/pbc/all/product-information-management/latest/base-shop/install-and-upgrade/upgrade-modules/upgrade-the-productconfigurationsrestapi-module.html&quot;&gt;Upgrade the ProductConfigurationsRestApi module&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/docs/pbc/all/product-information-management/latest/base-shop/install-and-upgrade/upgrade-modules/upgrade-the-productconfigurationwidget-module.html&quot;&gt;Upgrade the ProductConfigurationWidget module&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;/docs/pbc/all/product-information-management/latest/base-shop/install-and-upgrade/upgrade-modules/upgrade-the-productconfiguratorgatewaypage-module.html&quot;&gt;Upgrade the ProductConfiguratorGatewayPage module&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</description>
            <pubDate>Tue, 16 Jun 2026 14:58:00 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/product-information-management/latest/base-shop/feature-overviews/configurable-product-feature-overview/configurable-product-feature-overview.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/product-information-management/latest/base-shop/feature-overviews/configurable-product-feature-overview/configurable-product-feature-overview.html</guid>
            
            
        </item>
        
        <item>
            <title>Troubleshooting API Platform</title>
            <description>This document provides solutions to common issues when working with API Platform in Spryker.

## Generation issues

### Resources not generating

**Symptom:** Running `docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:generate` completes but no resources are created.

**Possible causes:**

1. **Schema file location is incorrect**

   ```bash
   ❌ src/Pyz/Glue/Customer/api/customers.resource.yml
   ✅ src/Pyz/Glue/Customer/resources/api/backend/customers.resource.yml
   ```

2. **API type not configured**

   Check `config/{APPLICATION}/packages/spryker_api_platform.php`:

   ```php
   $containerConfigurator-&gt;extension(&apos;spryker_api_platform&apos;, [
       &apos;api_types&apos; =&gt; [
           &apos;backend&apos;,  // Must match directory name
       ],
   ]);
   ```

3. **Bundle not registered**

   Verify `config/{APPLICATION}/bundles.php` includes:

   ```php
   SprykerApiPlatformBundle::class =&gt; [&apos;all&apos; =&gt; true],
   ```

**Solution:**

```bash
# Debug to see what&apos;s being discovered
docker/sdk cli glue  api:debug --list

# Check schema validation
docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:generate --validate-only

# Force regeneration
docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:generate --force
```

### Schema validation errors

**Symptom:** Generation fails with schema validation errors.

**Common errors:**

```bash
# Error: Invalid operation type
❌ operations:
    - type: CREATE

✅ operations:
    - type: Post

# Error: Invalid property type
❌ type: int
✅ type: integer

# Error: Missing resource name
❌ resource:
    shortName: customers

✅ resource:
    name: Customers
    shortName: customers
```

**Solution:**

1. Check schema against examples in documentation
2. Use `--validate-only` flag for detailed validation:

   ```bash
   docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:generate --validate-only
   ```

3. Inspect merged schema:

   ```bash
   docker/sdk cli glue  api:debug resource-name --show-merged
   ```

## Runtime issues

### Provider/Processor not found

**Symptom:**

```bash
Error: Class &quot;Pyz\Glue\Customer\Api\Backend\Provider\CustomerBackendProvider&quot; not found
```

**Possible causes:**

1. Class doesn&apos;t exist or namespace is wrong
2. Not registered in the Dependency Injection container
3. Typo in the schema file

**Solution:**

1. Verify the class exists and namespace matches:

   ```php
   namespace Pyz\Glue\Customer\Api\Backend\Provider;

   class CustomerBackendProvider implements ProviderInterface
   ```

2. Ensure services are auto-discovered in `ApplicationServices.php`:

   ```php
   $services-&gt;load(&apos;Pyz\\Glue\\&apos;, &apos;../../../src/Pyz/Glue/&apos;);
   ```

3. Check class name in the resource schema file of the module matches exactly:

   ```yaml
   provider: &quot;Pyz\\Glue\\Customer\\Api\\Backend\\Provider\\CustomerBackendProvider&quot;
   ```

### Validation not working

**Symptom:** API accepts invalid data despite validation rules.

**Possible causes:**

1. Validation schema file not found
2. Wrong operation name in validation schema
3. Validation groups not matching

**Solution:**

1. Ensure validation file exists:

   ```bash
   ✅ resources/api/backend/customers.validation.yml
   ```

2. Match operation names to HTTP methods:

   ```yaml
   post:      # For POST /customers
     email:
       - NotBlank

   patch:     # For PATCH /customers/{id}
     email:
       - Optional:
           constraints:
             - Email
   ```

3. Check generated resource class (for example `Generated\Api\Storefront\CustomersStorefrontResource`) has validation attributes:

   ```php
   #[Assert\NotBlank(groups: [&apos;customers:create&apos;])]
   #[Assert\Email(groups: [&apos;customers:create&apos;])]
   public ?string $email = null;
   ```

### API documentation UI not displaying correctly

**Symptom:** When accessing the root URL of your API application, you see:
- Missing styles/CSS
- Broken JavaScript functionality
- Plain HTML without formatting
- &quot;Failed to load resource&quot; errors in browser docker/sdk cli glue 

**Cause:** Assets were not installed after API Platform integration.

**Solution:**

Run the appropriate assets:install command for your application:

### For Glue application

```bash
docker/sdk cli glue assets:install public/Glue/assets  --symlink
```

### For GlueStorefront

```bash
docker/sdk cli GLUE_APPLICATION=GLUE_STOREFRONT glue assets:install public/GlueStorefront/assets/  --symlink
```

### For GlueBackend

```bash
docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue assets:install public/GlueBackend/assets/  --symlink
```

Then verify the documentation UI loads correctly by visiting the root URL:
- Storefront: `https://glue-storefront.mysprykershop.com/`
- Backend: `https://glue-backend.mysprykershop.com/`

{% info_block warningBox &quot;Required after integration&quot; %}

The `assets:install` command must be run after integrating API Platform and whenever API Platform assets are updated. This is a required step documented in [How to integrate API Platform](/docs/dg/dev/upgrade-and-migrate/integrate-api-platform.html).

{% endinfo_block %}

### 404 Not Found for API endpoints

**Symptom:** API requests return 404.

**Possible causes:**

1. Router not configured
2. Routes not loaded
3. Wrong URL format

**Solution:**

1. Verify `SymfonyFrameworkRouterPlugin` is registered:

   ```php
   // RouterDependencyProvider
   protected function getRouterPlugins(): array
   {
       return [
           new GlueRouterPlugin(),
           new SymfonyFrameworkRouterPlugin(), // Must be present
       ];
   }
   ```

2. Check API documentation for correct URLs:

   ```bash
   Storefront: https://glue-storefront.mysprykershop.com/
   Backend: https://glue-backend.mysprykershop.com/
   ```

   The interactive API documentation is available at the root URL of each application.

3. Use correct URL format:

   ```bash
   ❌ /api/v1/customers
   ✅ /customers
   ```

### Requests without an `Accept` header are rejected or return the wrong format

**Symptom:** A client request that omits the `Accept` header — or sends only `Accept: */*` — returns `406 Not Acceptable`, or a response in a format other than the legacy `application/vnd.api+json`. The legacy Glue REST API silently accepted the same request and answered with `application/vnd.api+json`.

**Cause:** API Platform runs content negotiation that requires a satisfiable `Accept` header and does not assume the legacy Glue default. This is a behavioral difference from the legacy Glue REST stack.

**Solution:**

1. Upgrade `spryker/api-platform` to **1.15.0 or higher**. Its `AcceptHeaderFallbackSubscriber` restores the legacy behavior — a missing or `*/*` `Accept` header defaults to `application/vnd.api+json`:

   ```bash
   composer update spryker/api-platform --with-dependencies
   ```

2. If you cannot upgrade, send an explicit `Accept` header from the client:

   ```bash
   curl -H &quot;Accept: application/vnd.api+json&quot; https://glue-backend.mysprykershop.com/customers
   ```

### Pagination not working

**Symptom:** All results returned instead of paginated response.

**Solution:**

1. Enable pagination in the schema file of the defining module:

   ```yaml
   resource:
     paginationEnabled: true
     paginationItemsPerPage: 10
   ```

2. Return `PaginatorInterface` from provider:

   ```php
   use ApiPlatform\State\Pagination\TraversablePaginator;

   return new TraversablePaginator(
       new \ArrayObject($results),
       $currentPage,
       $itemsPerPage,
       $totalItems
   );
   ```

3. Use pagination query parameters:

   ```bash
   GET /customers?page=2&amp;itemsPerPage=20
   ```

### Client cannot change items per page

**Symptom:** The `itemsPerPage` query parameter is ignored.

**Solution:**

Enable client-side items-per-page control and set a maximum limit in the resource schema:

```yaml
resource:
  paginationEnabled: true
  paginationItemsPerPage: 10
  paginationClientItemsPerPage: true
  paginationMaximumItemsPerPage: 100
```

Without `paginationClientItemsPerPage: true`, the `itemsPerPage` query parameter has no effect. The `paginationMaximumItemsPerPage` option prevents clients from requesting excessively large pages.

### Client cannot disable pagination

**Symptom:** The `pagination=false` query parameter is ignored and results are still paginated.

**Solution:**

Enable client-side pagination control in the resource schema:

```yaml
resource:
  paginationClientEnabled: true
```

Without `paginationClientEnabled: true`, the `pagination` query parameter has no effect.

For a full reference of all pagination options, see [Resource Schemas — Pagination](/docs/dg/dev/architecture/api-platform/resource-schemas.html#pagination).

## Dependency Injection issues

### Services not autowired

**Symptom:**

```bash
Cannot autowire service &quot;CustomerBackendProvider&quot;: argument &quot;$customerFacade&quot;
references class &quot;CustomerFacadeInterface&quot; but no such service exists.
```

**Solution:**

1. Register facade in the respective applications `ApplicationServices.php`:

   ```php
   use Pyz\Zed\Customer\Business\CustomerFacadeInterface;
   use Pyz\Zed\Customer\Business\CustomerFacade;

   $services-&gt;set(CustomerFacadeInterface::class, CustomerFacade::class);
   ```

2. Ensure constructor uses interface type hints:

   ```php
   public function __construct(
       private CustomerFacadeInterface $customerFacade,  // ✅ Interface
   ) {}
   ```

## Performance issues

### Slow API responses

**Symptom:** API endpoints respond slowly.

**Solution:**

1. Enable Symfony cache:

   ```bash
   docker/sdk cli glue  cache:warmup
   ```

2. Use pagination for collections
3. Optimize database queries in Provider
4. Use API Platform&apos;s built-in caching features

## Development tips

### Debugging schema merging

See which schemas contribute to final resource:

```bash
docker/sdk cli glue  api:debug customers --api-type=backend --show-sources
```

Output:

```bash
Source Files (priority order):
  ✓ vendor/spryker/customer/resources/api/backend/customers.resource.yml (CORE)
  ✓ src/SprykerFeature/CRM/resources/api/backend/customers.resource.yml (FEATURE)
  ✓ src/Pyz/Glue/Customer/resources/api/backend/customers.resource.yml (PROJECT)
```

### Inspecting generated code

View the generated resource class:

```bash
cat src/Generated/Api/Backend/CustomersBackendResource.php
```

Check for:
- Correct property types
- Validation attributes
- API Platform metadata

### Testing with dry-run

Preview generation without writing files:

```bash
docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:generate --dry-run
```

## Getting help

If you encounter issues not covered here:

1. **Check logs:**

   ```bash
   tail -f var/log/application.log
   tail -f var/log/exception.log
   ```

2. **Enable debug mode:**

   ```php
   // config/{APPLICATION}/packages/spryker_api_platform.php
   $containerConfigurator-&gt;extension(&apos;spryker_api_platform&apos;, [
       &apos;debug&apos; =&gt; true,
   ]);
   ```

3. **Validate environment:**

   ```bash
   php -v  # Check PHP version (8.3+)
   composer show | grep api-platform
   docker/sdk cli glue  debug:container | grep -i api
   ```

4. **Common error patterns:**

| Error | Likely cause | Solution |
|-------|--------------|----------|
| `Class not found` | Autoloading issue | Run `composer dump-autoload` |
| `Service not found` | DI configuration | Check `ApplicationServices.php` |
| `Route not found` | Router not configured | Add `SymfonyFrameworkRouterPlugin` |
| `Validation failed` | Schema mismatch | Regenerate with `--force` |
| `Cache is stale` | Outdated cache | Run `cache:clear` |
| API docs UI broken/unstyled | Assets not installed | Run `docker/sdk cli glue /glue assets:install` |

## Next steps

- [API Platform](/docs/dg/dev/architecture/api-platform.html) - Overview and concepts
- [How to integrate API Platform](/docs/dg/dev/upgrade-and-migrate/integrate-api-platform.html) - Setup guide
- [API Platform Enablement](/docs/dg/dev/architecture/api-platform/enablement.html) - Creating 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
- [API Platform Testing](/docs/dg/dev/architecture/api-platform/testing.html) - Testing guide
</description>
            <pubDate>Tue, 16 Jun 2026 12:18:37 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/architecture/api-platform/troubleshooting.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/architecture/api-platform/troubleshooting.html</guid>
            
            
        </item>
        
        <item>
            <title>How to migrate to API Platform</title>
            <description>{% info_block infoBox &quot;Start here for batch migration&quot; %}

If you&apos;re migrating multiple modules in one go (the default), follow the [API Platform migration overview](/docs/dg/dev/upgrade-and-migrate/migrate-to-api-platform-overview.html) first — it covers the shop-baseline upgrade, project-config checklist, and batch cleanup. This document is the per-module deep dive referenced from that overview.

{% endinfo_block %}

This document describes how to migrate existing Glue API resources to the API-Platform while maintaining backward compatibility.

## Overview

Migrating from Glue API to API Platform provides several benefits:

- **Schema-based development**: Define resources declaratively in YAML instead of PHP code
- **Automatic OpenAPI documentation**: Interactive API docs generated from schemas
- **Reduced boilerplate**: No need for manual resource builders, mappers, and route definitions
- **Built-in validation**: Declarative validation rules with operation-specific constraints
- **Standardized pagination**: Consistent pagination across all resources
- **Better maintainability**: Clearer separation of concerns with providers and processors

The recommended default is **batch migration** — migrating a group of related modules together, as described in the [API Platform migration overview](/docs/dg/dev/upgrade-and-migrate/migrate-to-api-platform-overview.html). The per-resource steps below are the mechanics you apply to each resource *within* a batch; none of it breaks existing API consumers.

## Prerequisites

Before migrating resources, ensure you have:

- Integrated API Platform as described in [How to integrate API Platform](/docs/dg/dev/upgrade-and-migrate/integrate-api-platform.html)
- Configured router plugins in correct order (see below)
- Tested that API Platform is working with at least one test resource

## Migration strategy and router setup

This guide covers the mechanics of migrating a single resource. The overall strategy (batch migration is the default), the router-plugin ordering, and how routing flips between Glue and API Platform are owned by the [API Platform migration overview](/docs/dg/dev/upgrade-and-migrate/migrate-to-api-platform-overview.html) — read it first. The steps below are what you apply to each resource within a batch.

## Migration process

### Step 1: Identify resources to migrate

List all existing Glue resources in your application:

Backend API resources are typically registered in:

`\Pyz\Glue\GlueBackendApiApplication\GlueBackendApiApplicationDependencyProvider::getResourcePlugins()`

Storefront API resources are typically registered in:

`\Pyz\Glue\GlueApplication\GlueApplicationDependencyProvider::getResourceRoutePlugins()`

Create a migration checklist:

```bash
[ ] Customers resource
[ ] Products resource
[ ] Orders resource
[ ] Cart resource
[ ] Wishlist resource
...
```

{% info_block infoBox &quot;Migration order recommendation&quot; %}

Start with simpler, read-only resources (GET operations only) before migrating complex resources with write operations and business logic.

{% endinfo_block %}

### Step 2: Analyze existing Glue resource

Before migrating, understand the existing resource structure.

**Example: Existing Glue Customer Resource**

1. **Resource route plugin:**
   `src/Pyz/Glue/CustomersRestApi/Plugin/GlueApplication/CustomersResourceRoutePlugin.php`

2. **Resource class:**
   `src/Pyz/Glue/CustomersRestApi/Processor/Customer/CustomerReader.php`

3. **Attributes transfer:**
   `src/Generated/Shared/Transfer/RestCustomersAttributesTransfer.php`

4. **Operations supported:**
   - GET `/customers/{customerReference}` - Get single customer
   - GET `/customers` - Get customer collection
   - POST `/customers` - Create customer
   - PATCH `/customers/{customerReference}` - Update customer

### Step 3: Create API Platform schema

Create the equivalent API Platform schema for the resource.

**Map Glue concepts to API Platform:**

| Glue API | API Platform |
|---------------|--------------|
| Resource class | Provider class |
| Resource builder | Schema definition (YAML) |
| Attributes transfer | Resource class (auto-generated) |
| Reader | Provider |
| Writer | Processor |
| Resource route plugin | Operations in schema |
| Relationship plugins | Properties in schema |

**Create schema file:**

`src/Pyz/Zed/Customer/resources/api/backend/customers.yml`

```yaml
resource:
    name: Customers
    shortName: Customer
    description: &quot;Customer resource for backend API&quot;

    provider: &quot;Pyz\\Glue\\Customer\\Api\\Backend\\Provider\\CustomerBackendProvider&quot;
    processor: &quot;Pyz\\Glue\\Customer\\Api\\Backend\\Processor\\CustomerBackendProcessor&quot;

    paginationEnabled: true
    paginationItemsPerPage: 10

    operations:
        - type: Post
        - type: Get
        - type: GetCollection
        - type: Patch

    properties:
        customerReference:
            type: string
            description: &quot;A unique reference for a customer.&quot;
            writable: false
            identifier: true

        email:
            type: string
            description: &quot;The email address of the customer.&quot;
            openapiContext:
                example: &quot;john.doe@example.com&quot;

        firstName:
            type: string
            description: &quot;The first name of the customer.&quot;
            openapiContext:
                example: &quot;John&quot;

        lastName:
            type: string
            description: &quot;The last name of the customer.&quot;
            openapiContext:
                example: &quot;Doe&quot;

        # Map all properties from RestCustomersAttributesTransfer
```

**Create validation schema:**

`src/Pyz/Zed/Customer/resources/api/backend/customers.validation.yml`

```yaml
post:
    email:
        - NotBlank:
            message: &quot;Email is required&quot;
        - Email:
            message: &quot;Invalid email format&quot;

    firstName:
        - NotBlank:
            message: &quot;First name is required&quot;

    lastName:
        - NotBlank:
            message: &quot;Last name is required&quot;

patch:
    email:
        - Optional:
            constraints:
                - Email
```

### Step 4: Implement Provider

Create the Provider to handle read operations, reusing existing business logic.

{% info_block infoBox &quot;Reuse existing business logic&quot; %}

The Provider should primarily call existing Facade methods. This ensures consistency and reduces duplication of business logic.

{% endinfo_block %}

`src/Pyz/Zed/Customer/Api/Backend/Provider/CustomerBackendProvider.php`

```php
&lt;?php

namespace Pyz\Zed\Customer\Api\Backend\Provider;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\Pagination\TraversablePaginator;
use ApiPlatform\State\ProviderInterface;
use Generated\Api\Backend\CustomersBackendResource;
use Spryker\Zed\Customer\Business\CustomerFacadeInterface;

class CustomerBackendProvider implements ProviderInterface
{
    public function __construct(
        private CustomerFacadeInterface $customerFacade,
    ) {
    }

    public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
    {
        if (isset($uriVariables[&apos;customerReference&apos;])) {
            return $this-&gt;getCustomer($uriVariables[&apos;customerReference&apos;]);
        }

        return $this-&gt;getCustomers($context);
    }

    private function getCustomer(string $customerReference): ?CustomersBackendResource
    {
        // Reuse existing Glue logic
        $customerTransfer = $this-&gt;customerFacade-&gt;findCustomerByReference($customerReference);

        if ($customerTransfer === null) {
            return null;
        }

        // Map transfer to API Platform resource
        $resource = new CustomersBackendResource();
        $resource-&gt;fromArray($customerTransfer-&gt;toArray());

        return $resource;
    }

    private function getCustomers(array $context): TraversablePaginator
    {
        $filters = $context[&apos;filters&apos;] ?? [];
        $page = (int) ($filters[&apos;page&apos;] ?? 1);
        $itemsPerPage = (int) ($filters[&apos;itemsPerPage&apos;] ?? 10);

        // Reuse existing facade method
        $customerCollection = $this-&gt;customerFacade-&gt;getCustomerCollection($page, $itemsPerPage);

        $resources = [];
        foreach ($customerCollection-&gt;getCustomers() as $customerTransfer) {
            $resource = new CustomersBackendResource();
            $resource-&gt;fromArray($customerTransfer-&gt;toArray());
            $resources[] = $resource;
        }

        return new TraversablePaginator(
            new \ArrayObject($resources),
            $page,
            $itemsPerPage,
            $customerCollection-&gt;getTotalCount()
        );
    }
}
```

### Step 5: Implement Processor

Create the Processor to handle write operations.

{% info_block infoBox &quot;Reuse existing business logic&quot; %}

The Processor should primarily call existing Facade methods. This ensures consistency and reduces duplication of business logic.

{% endinfo_block %}

`src/Pyz/Zed/Customer/Api/Backend/Processor/CustomerBackendProcessor.php`

```php
&lt;?php

namespace Pyz\Zed\Customer\Api\Backend\Processor;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use ApiPlatform\State\ProcessorInterface;
use Generated\Api\Backend\CustomersBackendResource;
use Generated\Shared\Transfer\CustomerTransfer;
use Spryker\Zed\Customer\Business\CustomerFacadeInterface;

class CustomerBackendProcessor implements ProcessorInterface
{
    public function __construct(
        private CustomerFacadeInterface $customerFacade,
    ) {
    }

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
    {
        if ($operation instanceof Post) {
            return $this-&gt;createCustomer($data);
        }

        if ($operation instanceof Patch) {
            return $this-&gt;updateCustomer($data, $uriVariables[&apos;customerReference&apos;]);
        }

        return null;
    }

    private function createCustomer(CustomersBackendResource $resource): CustomersBackendResource
    {
        $customerTransfer = new CustomerTransfer();
        $customerTransfer-&gt;fromArray($resource-&gt;toArray(), true);

        // Reuse existing facade method
        $customerResponseTransfer = $this-&gt;customerFacade-&gt;addCustomer($customerTransfer);

        $result = new CustomersBackendResource();
        $result-&gt;fromArray($customerResponseTransfer-&gt;getCustomerTransfer()-&gt;toArray());

        return $result;
    }

    private function updateCustomer(CustomersBackendResource $resource, string $customerReference): CustomersBackendResource
    {
        $customerTransfer = new CustomerTransfer();
        $customerTransfer-&gt;fromArray($resource-&gt;toArray(), true);
        $customerTransfer-&gt;setCustomerReference($customerReference);

        // Reuse existing facade method
        $customerResponseTransfer = $this-&gt;customerFacade-&gt;updateCustomer($customerTransfer);

        $result = new CustomersBackendResource();
        $result-&gt;fromArray($customerResponseTransfer-&gt;getCustomerTransfer()-&gt;toArray());

        return $result;
    }
}
```

### Step 6: Generate API Platform resource

Generate the backend resource class from the schema:

```bash
docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:generate

# Verify generation
ls -la src/Generated/Api/Backend/CustomersBackendResource.php
```

### Step 7: Test the API Platform endpoint

Test that the new endpoint works correctly:

```bash
# Test single resource
curl -X GET http://glue-backend.eu.spryker.local/customers/DE--1

# Test collection
curl -X GET http://glue-backend.eu.spryker.local/customers?page=1&amp;itemsPerPage=10

# Test create
curl -X POST http://glue-backend.eu.spryker.local/customers \
  -H &quot;Content-Type: application/json&quot; \
  -d &apos;{&quot;email&quot;:&quot;test@example.com&quot;,&quot;firstName&quot;:&quot;John&quot;,&quot;lastName&quot;:&quot;Doe&quot;}&apos;

# Test update
curl -X PATCH http://glue-backend.eu.spryker.local/customers/DE--1 \
  -H &quot;Content-Type: application/json&quot; \
  -d &apos;{&quot;firstName&quot;:&quot;Jane&quot;}&apos;
```

Verify:
- ✅ Responses match expected format
- ✅ Validation rules work correctly
- ✅ Error handling is appropriate
- ✅ Pagination works for collections
- ✅ OpenAPI documentation is generated at root URL `/`

### Step 8: Run existing Glue API tests

Ensure backward compatibility by running existing tests:

```bash
# Run Glue API tests
vendor/bin/codecept run -c tests/PyzTest/Glue/CustomersRestApi

# Or specific test
vendor/bin/codecept run -c tests/PyzTest/Glue/CustomersRestApi/RestApi/CustomerRestApiCest
```

All existing tests should still pass because:
- `GlueRouterPlugin` is checked first
- Existing Glue endpoints still work
- No breaking changes to consumers

### Step 9: Remove Glue resource files

{% info_block warningBox &quot;Plugin removal is the migration switch&quot; %}

The actual switch from Glue REST to API Platform for this module is removing its `*ResourceRoutePlugin` from the project-level dependency provider (shown below). The optional `excludedPathFragments` setting in `spryker_api_platform.php` controls schema generation only — it does not flip routing. The `spryker/&lt;module&gt;-rest-api` composer package may stay installed; it simply no longer serves routes once the plugin is unregistered.

{% endinfo_block %}

Once the API Platform endpoint is working and tested, remove the old Glue files:

```bash
# Remove resource route plugin
rm src/Pyz/Glue/CustomersRestApi/Plugin/GlueApplication/CustomersResourceRoutePlugin.php

# Remove processor classes
rm -rf src/Pyz/Glue/CustomersRestApi/Processor/

# Update dependency provider to remove plugin registration
```

**Update GlueApplicationDependencyProvider:**

`src/Pyz/Glue/GlueApplication/GlueApplicationDependencyProvider.php`

```php
protected function getResourceRoutePlugins(): array
{
    return [
        // new CustomersResourceRoutePlugin(), // ← Remove this line
        new ProductsResourceRoutePlugin(),
        new OrdersResourceRoutePlugin(),
        // ... keep other plugins
    ];
}
```

### Step 10: Verify migration

After removing Glue resource files:

```bash
# Clear caches
console cache:clear

# Test that API Platform endpoint still works
curl -X GET http://glue-backend.eu.spryker.local/customers/DE--1

# Verify OpenAPI docs include the resource
curl http://glue-backend.eu.spryker.local/docs.json | jq &apos;.paths&apos;

# Check the interactive documentation at root URL
# Visit: http://glue-backend.eu.spryker.local/
```

### Step 11: Repeat for remaining resources

Repeat steps 2-10 for each resource in your migration checklist:

```bash
[✓] Customers resource     ← Migrated
[ ] Products resource      ← Next
[ ] Orders resource
[ ] Cart resource
[ ] Wishlist resource
...
```

## Migration comparison

### Before: Glue API

```bash
Request: GET /customers/DE--1
    ↓
GlueRouterPlugin
    ↓
CustomersResourceRoutePlugin
    ↓
CustomerReaderInterface
    ↓
CustomerFacade
    ↓
RestResourceBuilder
    ↓
Response: RestCustomersAttributesTransfer
```

### After: API Platform

```bash
Request: GET /customers/DE--1
    ↓
SymfonyFrameworkRouterPlugin
    ↓
API Platform Router
    ↓
CustomerBackendProvider
    ↓
CustomerFacade (same!)
    ↓
CustomersBackendResource
    ↓
Response: JSON (auto-serialized)
```

## Key differences

| Aspect | Glue API | API Platform |
|--------|----------|--------------|
| **Definition** | PHP classes &amp; plugins | YAML schemas |
| **Routing** | ResourceRoutePlugin | Schema operations |
| **Reading data** | Reader classes | Provider classes |
| **Writing data** | Writer classes | Processor classes |
| **Validation** | Manual in reader/writer | Declarative in validation schema |
| **Documentation** | Separate OpenAPI schema | Auto-generated from schema |
| **Response building** | Manual RestResourceBuilder | Auto-serialization |
| **Relationships** | Relationship plugins | Schema properties |
| **File count** | ~10-15 files per resource | ~3-5 files per resource |

## Troubleshooting migration

### Both old and new endpoints respond

**Symptom:** Both Glue and API Platform endpoints return responses.

**Cause:** Different URLs are being used. Check if they&apos;re actually the same:

```bash
# Glue endpoint
GET /customers/DE--1

# API Platform endpoint
GET /customers/DE--1

# Check URL prefixes in configuration
```

**Solution:** Ensure URLs match exactly. API Platform resources use `shortName` for URL generation.

### API Platform endpoint returns 404 during migration

**Symptom:** After creating schema and generating resource, endpoint returns 404.

**Possible causes:**

1. Router order is wrong (SymfonyFrameworkRouterPlugin before GlueRouterPlugin)
2. Cache not cleared
3. Resource not generated

**Solution:**

```bash
# Check router order in RouterDependencyProvider
# Should be: GlueRouterPlugin, then SymfonyFrameworkRouterPlugin

# Clear caches
console cache:clear

# Regenerate resources
docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:generate

# Verify generated file exists
ls -la src/Generated/Api/Backend/CustomersBackendResource.php
```

### Different response format between Glue and API Platform

**Symptom:** API Platform returns different JSON structure than Glue.

**Cause:** Glue uses JSON:API format, API Platform uses JSON-LD by default which is configurable and depending on your needs you can migrate to JSON-LD as well or stay with the JSON API format. API-Platform covers this possibility for you

**Solution:**

This is expected. You have three options:

1. **Accept the difference** (recommended): Update API consumers to handle both formats during migration
2. **Configure API Platform format**: Customize serialization to match the Glue format. See [Serialization](/docs/dg/dev/architecture/api-platform/serialization.html) for how API Platform serialization works and how to register custom normalizers.
3. **Use content negotiation**: Support both formats based on `Accept` header

### Business logic differs between implementations

**Symptom:** API Platform endpoint behaves differently than a Glue endpoint.

**Cause:** Provider/Processor uses different facade methods or has different logic.

**Solution:**

Review and ensure both use the same facade methods:

```php
// Glue Reader
$customerReader-&gt;readCustomer($customerReference);
    ↓ calls
$this-&gt;customerFacade-&gt;findCustomerByReference($customerReference);

// API Platform Provider
$this-&gt;customerFacade-&gt;findCustomerByReference($customerReference); // ← Same method!
```

## Best practices

### 1. Keep batches small

Batch migration is the default (see the [migration overview](/docs/dg/dev/upgrade-and-migrate/migrate-to-api-platform-overview.html)), but keep each batch small and ship it before starting the next — don&apos;t try to migrate every resource in one go. For example:

```bash
Sprint 1: Customers, Products (read-only)
Sprint 2: Orders, Cart
Sprint 3: Wishlist, Checkout
```

### 2. Keep business logic in facades

Don&apos;t duplicate business logic in Providers/Processors:

```php
// ❌ Bad: Logic in Provider
private function getCustomer(string $reference): ?CustomersBackendResource
{
    $customer = $this-&gt;repository-&gt;findByReference($reference);
    // ... business logic here
}

// ✅ Good: Delegate to Facade
private function getCustomer(string $reference): ?CustomersBackendResource
{
    $customerTransfer = $this-&gt;customerFacade-&gt;findCustomerByReference($reference);
    return $this-&gt;mapToResource($customerTransfer);
}
```

### 3. Use toArray/fromArray for mapping

Leverage generated `toArray()` and `fromArray()` methods:

```php
// Easy mapping between Transfer and Resource
$resource = new CustomersBackendResource();
$resource-&gt;fromArray($customerTransfer-&gt;toArray());
```

### 4. Test thoroughly before removing Glue code

- Run all existing tests
- Perform manual testing
- Check with API consumers
- Monitor production traffic

### 5. Document breaking changes

If response formats differ, document changes for API consumers:

```markdown
## Migration Notice: Customers API

The `/customers` endpoint is being migrated to API-Platform.

### Changes:
- Response format: JSON:API → JSON-LD
- Date format: unix timestamp → ISO 8601
- Error format: JSON:API errors → RFC 7807 Problem Details

### Timeline:
- Old endpoint: Supported until 2026-12-31
- New endpoint: Available now
- Deprecation: Old endpoint will return deprecation headers starting 2026-09-01
```

## Next steps

- [API Platform](/docs/dg/dev/architecture/api-platform.html) - Architecture overview
- [API Platform Enablement](/docs/dg/dev/architecture/api-platform/enablement.html) - Creating resources
- [Resource Schemas](/docs/dg/dev/architecture/api-platform/resource-schemas.html) - Resource Schemas
- [Validation Schemas](/docs/dg/dev/architecture/api-platform/validation-schemas.html) - Validation Schemas
- [Troubleshooting](/docs/dg/dev/architecture/api-platform/troubleshooting.html) - Common issues
</description>
            <pubDate>Tue, 16 Jun 2026 12:18:37 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/upgrade-and-migrate/migrate-to-api-platform.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/upgrade-and-migrate/migrate-to-api-platform.html</guid>
            
            
        </item>
        
    </channel>
</rss>
