<?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>Tue, 16 Jun 2026 16:58:09 +0000</lastBuildDate>
        <generator>Jekyll v4.2.2</generator>
        
        
        <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>
        
        <item>
            <title>Migration status - Glue API to API Platform</title>
            <description>This document tracks Spryker&apos;s migration of API-providing modules to the **API Platform** (built on Symfony and the API Platform library). Use it to plan upgrades and check the current status of every module.

{% info_block infoBox &quot;Looking for the integration guide?&quot; %}

This page does **not** describe how to integrate API Platform into your project. For step-by-step integration instructions, see:

- [Migrate to API Platform](/docs/dg/dev/upgrade-and-migrate/migrate-to-api-platform.html)
- [Integrate API Platform](/docs/dg/dev/upgrade-and-migrate/integrate-api-platform.html)
- [Integrate API Platform security](/docs/dg/dev/upgrade-and-migrate/integrate-api-platform-security.html)
- [API Platform architecture](/docs/dg/dev/architecture/api-platform.html)

{% endinfo_block %}

## How to migrate a module

The end-to-end migration steps — upgrade the module, confirm configuration, flip routing, verify, and clean up — are owned by the [API Platform migration overview](/docs/dg/dev/upgrade-and-migrate/migrate-to-api-platform-overview.html). This page only tracks **which** modules are available on API Platform and their status.

## Status legend

| Status | Meaning |
|---|---|
| Migrated | Module is available on API Platform and production-ready. |
| Planned | Module is scheduled or queued for migration to API Platform. |

## Storefront API modules

All StorefrontAPI and Extension-only StorefrontAPI modules. Migrated modules are listed first.

| Module | Category | Status | Released In | Requires | Key endpoints |
|---|---|---|---|---|---|
| ContentProductAbstractListsRestApi | StorefrontAPI | Migrated | 1.4.0 | ProductsRestApi | GET /content-product-abstract-lists/{id}&lt;br&gt;GET /content-product-abstract-lists/{id}/abstract-products |
| MerchantOpeningHoursRestApi | StorefrontAPI | Migrated | 1.2.0 | — | GET /merchants/{id}/merchant-opening-hours |
| MerchantCategoriesRestApi | Extension-only StorefrontAPI | Migrated | 1.2.0 | MerchantsRestApi | (extension-only) |
| MerchantProductOffersRestApi | StorefrontAPI | Migrated | 2.2.0 | — | GET /concrete-products/{id}/product-offers&lt;br&gt;GET /product-offers/{id} |
| MerchantProductOfferServicePointAvailabilitiesRestApi | Extension-only StorefrontAPI | Migrated | 0.4.0 | ProductOfferServicePointAvailabilitiesRestApi | (extension-only) |
| MerchantsRestApi | StorefrontAPI | Migrated | 1.2.0 | — | GET /merchants&lt;br&gt;GET /merchants/{id}&lt;br&gt;GET /merchants/{id}/merchant-addresses |
| OrderPaymentsRestApi | StorefrontAPI | Migrated | 1.2.0 | — | POST /order-payments |
| PaymentsRestApi | StorefrontAPI | Migrated | 1.7.0 | — | POST /payments&lt;br&gt;POST /payment-cancellations&lt;br&gt;POST /payment-customers |
| ProductAvailabilitiesRestApi | StorefrontAPI | Migrated | 4.4.0 | ProductsRestApi | GET /abstract-products/{id}/abstract-product-availabilities&lt;br&gt;GET /concrete-products/{id}/concrete-product-availabilities |
| ProductOfferAvailabilitiesRestApi | StorefrontAPI | Migrated | 1.3.0 | — | GET /product-offers/{id}/product-offer-availabilities |
| ProductOfferServicePointAvailabilitiesRestApi | StorefrontAPI | Migrated | 1.2.0 | — | POST /product-offer-service-point-availabilities |
| ProductOfferPricesRestApi | StorefrontAPI | Migrated | 2.5.0 | — | GET /product-offers/{id}/product-offer-prices |
| ProductPricesRestApi | StorefrontAPI | Migrated | 1.12.0 | ProductsRestApi | GET /abstract-products/{id}/abstract-product-prices&lt;br&gt;GET /concrete-products/{id}/concrete-product-prices |
| ProductTaxSetsRestApi | StorefrontAPI | Migrated | 2.3.0 | — | GET /abstract-products/{id}/product-tax-sets |
| ProductsRestApi | StorefrontAPI | Migrated | 2.17.0 | — | GET /abstract-products/{id}&lt;br&gt;GET /concrete-products/{id} |
| ShipmentTypeProductOfferServicePointAvailabilitiesRestApi | Extension-only StorefrontAPI | Migrated | 1.2.0 | ProductOfferServicePointAvailabilitiesRestApi | (extension-only) |
| StoresApi | StorefrontAPI | Migrated | 1.3.0 | — | GET /stores |
| AgentAuthRestApi | StorefrontAPI | Migrated | 1.3.0 | — | POST /agent-access-tokens&lt;br&gt;POST /agent-customer-impersonation-access-tokens&lt;br&gt;GET /agent-customer-search |
| AlternativeProductsRestApi | StorefrontAPI | Migrated | 1.3.0 | ProductsRestApi | GET /abstract-products/{id}/related-products&lt;br&gt;GET /concrete-products/{id}/abstract-alternative-products&lt;br&gt;GET /concrete-products/{id}/concrete-alternative-products |
| AuthRestApi | StorefrontAPI | Migrated | 2.17.0 | — | POST /token&lt;br&gt;POST /access-tokens&lt;br&gt;POST /refresh-tokens&lt;br&gt;DELETE /refresh-tokens/{id} |
| AvailabilityNotificationsRestApi | StorefrontAPI | Migrated | 1.4.0 | — | POST /availability-notifications&lt;br&gt;DELETE /availability-notifications/{id}&lt;br&gt;GET /my-availability-notifications&lt;br&gt;GET /customers/{id}/availability-notifications |
| CartCodesRestApi | StorefrontAPI | Migrated | 1.7.0 | CartsRestApi | POST /carts/{id}/cart-codes&lt;br&gt;DELETE /carts/{id}/cart-codes/{id}&lt;br&gt;POST /guest-carts/{id}/cart-codes&lt;br&gt;DELETE /guest-carts/{id}/cart-codes/{id} |
| CartPermissionGroupsRestApi | StorefrontAPI | Migrated | 1.4.0 | — | GET /cart-permission-groups&lt;br&gt;GET /cart-permission-groups/{id} |
| CartReorderRestApi | StorefrontAPI | Migrated | 1.3.0 | CartsRestApi | POST /cart-reorder |
| CartsRestApi | StorefrontAPI | Migrated | 5.25.0 | — | GET,POST /carts&lt;br&gt;GET,PATCH,DELETE /carts/{id}&lt;br&gt;POST /carts/{id}/items&lt;br&gt;PATCH,DELETE /carts/{id}/items/{id}&lt;br&gt;GET /guest-carts&lt;br&gt;GET,PATCH /guest-carts/{id}&lt;br&gt;POST /guest-carts/{id}/guest-cart-items&lt;br&gt;PATCH,DELETE /guest-carts/{id}/guest-cart-items/{id}&lt;br&gt;GET /customers/{id}/carts |
| CatalogSearchRestApi | StorefrontAPI | Migrated | 2.13.0 | — | GET /catalog-search&lt;br&gt;GET /catalog-search-suggestions |
| CategoriesRestApi | StorefrontAPI | Migrated | 1.9.0 | — | GET /category-trees&lt;br&gt;GET /category-nodes/{id} |
| CheckoutRestApi | StorefrontAPI | Migrated | 3.14.0 | CartsRestApi | POST /checkout-data&lt;br&gt;POST /checkout |
| CmsPagesRestApi | StorefrontAPI | Migrated | 1.2.0 | — | GET /cms-pages&lt;br&gt;GET /cms-pages/{id} |
| CompaniesRestApi | StorefrontAPI | Migrated | 1.5.0 | — | GET /companies&lt;br&gt;GET /companies/{id} |
| CompanyBusinessUnitAddressesRestApi | StorefrontAPI | Migrated | 1.4.0 | — | GET /company-business-unit-addresses&lt;br&gt;GET /company-business-unit-addresses/{id} |
| CompanyBusinessUnitsRestApi | StorefrontAPI | Migrated | 1.6.0 | — | GET /company-business-units&lt;br&gt;GET /company-business-units/{id} |
| CompanyRolesRestApi | StorefrontAPI | Migrated | 1.3.0 | — | GET /company-roles&lt;br&gt;GET /company-roles/{id} |
| CompanyUserAuthRestApi | StorefrontAPI | Migrated | 2.3.0 | — | POST /company-user-access-tokens |
| CompanyUsersRestApi | StorefrontAPI | Migrated | 2.11.0 | — | GET /company-users&lt;br&gt;GET /company-users/{id} |
| ConfigurableBundleCartsRestApi | StorefrontAPI | Migrated | 1.2.0 | CartsRestApi | POST /carts/{id}/configured-bundles&lt;br&gt;PATCH,DELETE /carts/{id}/configured-bundles/{id}&lt;br&gt;POST,PATCH,DELETE /guest-carts/{id}/guest-configured-bundles/{id} |
| ConfigurableBundlesRestApi | StorefrontAPI | Migrated | 1.3.0 | — | GET /configurable-bundle-templates&lt;br&gt;GET /configurable-bundle-templates/{id} |
| ContentBannersRestApi | StorefrontAPI | Migrated | 2.4.0 | — | GET /content-banners/{id} |
| CustomerAccessRestApi | StorefrontAPI | Migrated | 1.3.0 | — | GET /customer-access |
| CustomersRestApi | StorefrontAPI | Migrated | 1.28.0 | — | GET,POST /customers&lt;br&gt;GET,PATCH,DELETE /customers/{id}&lt;br&gt;GET,POST /customers/{id}/addresses&lt;br&gt;GET,PATCH,DELETE /customers/{id}/addresses/{id}&lt;br&gt;POST /customer-forgotten-password&lt;br&gt;PATCH /customer-restore-password/{id}&lt;br&gt;PATCH /customer-password/{id}&lt;br&gt;POST /customer-confirmation |
| DiscountPromotionsRestApi | Extension-Only-StorefrontAPI | Migrated | 1.6.0 | CartCodesRestApi, CartsRestApi | (extension-only) |
| EntityTagsRestApi | Extension-only StorefrontAPI | Migrated | 1.1.0 | — | (extension-only) |
| GiftCardsRestApi | Extension-only StorefrontAPI | Migrated | 1.2.0 | — | (extension-only) |
| HealthCheck | StorefrontAPI | Migrated | 1.1.0 | — | GET /health-check |
| MerchantProductOfferShoppingListsRestApi | Extension-only StorefrontAPI | Migrated | 1.2.0 | — | (extension-only) |
| MerchantProductOfferWishlistRestApi | Extension-only StorefrontAPI | Migrated | 1.3.0 | WishlistsRestApi | (extension-only) |
| MerchantProductShoppingListsRestApi | Extension-only StorefrontAPI | Migrated | 1.2.0 | — | (extension-only) |
| MerchantProductsRestApi | Extension-only StorefrontAPI | Migrated | 1.1.0 | CartsRestApi | (extension-only) |
| MerchantSalesReturnsRestApi | Extension-only StorefrontAPI | Migrated | 1.1.0 | — | (extension-only) |
| MerchantShipmentsRestApi | Extension-only StorefrontAPI | Migrated | 0.1.1 | ShipmentsRestApi | (extension-only) |
| MultiCartsRestApi | Extension-only StorefrontAPI | Migrated | 1.1.0 | CartsRestApi | (extension-only) |
| MultiFactorAuth | StorefrontAPI | Migrated | 2.5.0 | — | GET /multi-factor-auth-types, POST /multi-factor-auth-trigger, POST /multi-factor-auth-type-activate, POST /multi-factor-auth-type-verify, POST /multi-factor-auth-type-deactivate |
| NavigationsRestApi | StorefrontAPI | Migrated | 2.3.0 | — | GET /navigations/{id} |
| OauthApi | StorefrontAPI | Migrated | 1.4.1 | — | POST /token |
| OrderAmendmentsRestApi | Extension-only StorefrontAPI | Migrated | 1.2.0 | CartReorderRestApi, CartsRestApi, OrdersRestApi | (extension-only) |
| OrdersRestApi | StorefrontAPI | Migrated | 4.14.0 | — | GET /orders&lt;br&gt;GET /orders/{orderReference}&lt;br&gt;GET /orders/{orderReference}/order-items/{uuid}&lt;br&gt;GET /customers/{customerReference}/orders |
| PriceProductOfferVolumesRestApi | Extension-only StorefrontAPI | Migrated | 1.1.1 | ProductOfferPricesRestApi | (extension-only) |
| PriceProductVolumesRestApi | Extension-only StorefrontAPI | Migrated | 1.2.0 | ProductPricesRestApi | (extension-only) |
| ProductAttributesRestApi | StorefrontAPI | Migrated | 1.3.0 | — | GET /product-management-attributes&lt;br&gt;GET /product-management-attributes/{id} |
| ProductBundleCartsRestApi | Extension-only StorefrontAPI | Migrated | 1.4.0 | CartsRestApi, ShipmentsRestApi | (extension-only) |
| ProductBundlesRestApi | StorefrontAPI | Migrated | 1.2.0 | OrdersRestApi | GET /concrete-products/{id}/bundled-products |
| ProductConfigurationShoppingListsRestApi | Extension-only StorefrontAPI | Migrated | 1.2.0 | ShoppingListsRestApi | (extension-only) |
| ProductConfigurationWishlistsRestApi | Extension-only StorefrontAPI | Migrated | 1.3.0 | WishlistsRestApi | (extension-only) |
| ProductConfigurationsPriceProductVolumesRestApi | Extension-only StorefrontAPI | Migrated | 1.1.0 | ProductConfigurationShoppingListsRestApi, ProductConfigurationWishlistsRestApi, ProductConfigurationsRestApi | (extension-only) |
| ProductConfigurationsRestApi | Extension-only StorefrontAPI | Migrated | 1.2.0 | CartsRestApi, OrdersRestApi, ProductsRestApi | (extension-only) |
| ProductDiscontinuedRestApi | Extension-only StorefrontAPI | Migrated | 1.1.1 | ProductsRestApi | (extension-only) |
| ProductImageSetsRestApi | StorefrontAPI | Migrated | 1.3.0 | ProductsRestApi | GET /abstract-products/{id}/abstract-product-image-sets&lt;br&gt;GET /concrete-products/{id}/concrete-product-image-sets |
| ProductLabelsRestApi | StorefrontAPI | Migrated | 1.5.0 | — | GET /product-labels/{id} |
| ProductMeasurementUnitsRestApi | StorefrontAPI | Migrated | 1.3.0 | — | GET /product-measurement-units/{id}&lt;br&gt;GET /concrete-products/{id}/sales-units |
| ProductOfferSalesRestApi | Extension-only StorefrontAPI | Migrated | 1.2.0 | — | (extension-only) |
| ProductOfferShoppingListsRestApi | Extension-only StorefrontAPI | Migrated | 1.2.0 | — | (extension-only) |
| ProductOffersRestApi | Extension-only StorefrontAPI | Migrated | 1.1.0 | ProductsRestApi | (extension-only) |
| ProductOptionsRestApi | Extension-only StorefrontAPI | Migrated | 1.5.0 | CartsRestApi, OrdersRestApi, ProductsRestApi, QuoteRequestsRestApi | (extension-only) |
| ProductReviewsRestApi | StorefrontAPI | Migrated | 1.3.0 | — | GET,POST /abstract-products/{id}/product-reviews&lt;br&gt;GET /abstract-products/{id}/product-reviews/{id} |
| QuoteRequestAgentsRestApi | StorefrontAPI | Migrated | 0.4.2 | QuoteRequestsRestApi | GET,POST /agent-quote-requests&lt;br&gt;GET,PATCH /agent-quote-requests/{id}&lt;br&gt;POST /agent-quote-requests/{id}/agent-quote-request-cancel&lt;br&gt;POST /agent-quote-requests/{id}/agent-quote-request-revise&lt;br&gt;POST /agent-quote-requests/{id}/agent-quote-request-send-to-customer |
| QuoteRequestsRestApi | StorefrontAPI | Migrated | 0.2.2 | CartsRestApi | GET,POST /quote-requests&lt;br&gt;GET,PATCH /quote-requests/{id}&lt;br&gt;POST /quote-requests/{id}/quote-request-cancel&lt;br&gt;POST /quote-requests/{id}/quote-request-revise&lt;br&gt;POST /quote-requests/{id}/quote-request-send-to-user&lt;br&gt;POST /quote-requests/{id}/quote-request-convert-to-quote |
| RelatedProductsRestApi | StorefrontAPI | Migrated | 1.5.0 | ProductsRestApi | GET /abstract-products/{id}/related-products |
| SalesOrderThresholdsRestApi | Extension-only StorefrontAPI | Migrated | 1.1.0 | CartsRestApi, CheckoutRestApi | (extension-only) |
| SalesReturnsRestApi | StorefrontAPI | Migrated | 1.3.0 | — | GET /return-reasons&lt;br&gt;GET,POST /returns&lt;br&gt;GET /returns/{id} |
| SecurityBlockerRestApi | Extension-only StorefrontAPI | Migrated | 1.1.0 | — | (extension-only) |
| ServicePointCartsRestApi | Extension-only StorefrontAPI | Migrated | 1.1.0 | CheckoutRestApi | (extension-only) |
| ServicePointsRestApi | StorefrontAPI | Migrated | 1.2.0 | — | GET /service-points&lt;br&gt;GET /service-points/{id}&lt;br&gt;GET /service-points/{id}/service-point-addresses/{id} |
| SharedCartsRestApi | StorefrontAPI | Migrated | 1.4.0 | — | POST /carts/{id}/shared-carts&lt;br&gt;PATCH,DELETE /shared-carts/{id} |
| ShipmentTypeServicePointsRestApi | Extension-only StorefrontAPI | Migrated | 1.1.0 | CheckoutRestApi, ServicePointsRestApi, ShipmentTypesRestApi, ShipmentsRestApi | (extension-only) |
| ShipmentTypesRestApi | StorefrontAPI | Migrated | 1.2.0 | — | GET /shipment-types&lt;br&gt;GET /shipment-types/{id} |
| ShipmentsRestApi | Extension-only StorefrontAPI | Migrated | 1.16.0 | CheckoutRestApi, OrdersRestApi, QuoteRequestsRestApi | (extension-only) |
| ShoppingListsRestApi | StorefrontAPI | Migrated | 1.5.0 | — | GET,POST /shopping-lists&lt;br&gt;GET,PATCH,DELETE /shopping-lists/{id}&lt;br&gt;POST /shopping-lists/{id}/shopping-list-items&lt;br&gt;PATCH,DELETE /shopping-lists/{id}/shopping-list-items/{id} |
| UpSellingProductsRestApi | StorefrontAPI | Migrated | 1.4.0 | CartsRestApi, ProductsRestApi | GET /carts/{id}/up-selling-products&lt;br&gt;GET /guest-carts/{id}/up-selling-products |
| UrlsRestApi | StorefrontAPI | Migrated | 1.2.0 | — | GET /url-resolver |
| Vertex | StorefrontAPI | Migrated | 1.2.0 | — | POST /tax-id-validate |
| WishlistsRestApi | StorefrontAPI | Migrated | 1.8.0 | — | GET,POST /wishlists&lt;br&gt;GET,PATCH,DELETE /wishlists/{id}&lt;br&gt;POST /wishlists/{id}/wishlist-items&lt;br&gt;PATCH,DELETE /wishlists/{id}/wishlist-items/{id} |

### Backward-compatible extension modules (no migration required)

The following modules are not migrated to API Platform and do not need to be. They expose no API resource of their own — they only contribute plugins (mappers and expanders) that are consumed in a backward-compatible way by both the legacy Glue REST API and the API Platform resources of the host module they extend. Because they carry no standalone resource, they have no &quot;Released In&quot; version.

| Module | Plugin provided | Consumed by |
|---|---|---|
| DiscountsRestApi | DiscountsRestQuoteRequestAttributesExpanderPlugin | QuoteRequestsRestApi |
| MerchantRelationshipProductListsRestApi | CustomerProductListCustomerExpanderPlugin | CustomersRestApi |
| OmsRestApi | OmsRestOrderItemsAttributesMapperPlugin | OrdersRestApi |

## Backend API modules

All BackendAPI modules tracked in the migration scope.

| Module | Category | Status | Released In | Requires | Key endpoints |
|---|---|---|---|---|---|
| CartNotesBackendApi | Extension-Only BackendAPI | Planned | — | SalesOrdersBackendApi | (extension-only) |
| CategoriesBackendApi | BackendAPI | Planned | — | — | GET,POST /categories&lt;br&gt;GET,PATCH /categories/{id} |
| DynamicEntityBackendApi | BackendAPI | Planned | — | — | GET,POST,PATCH,PUT /dynamic-entity/{entity-name} (~62 auto-generated entity endpoints) |
| OauthBackendApi | BackendAPI | Planned | — | — | POST /token |
| PickingListsBackendApi | BackendAPI | Planned | — | — | GET /picking-lists&lt;br&gt;GET /picking-lists/{id}&lt;br&gt;PATCH /picking-lists/{id}/picking-list-items/{id}&lt;br&gt;POST /start-picking |
| PickingListsUsersBackendApi | Extension-Only BackendAPI | Planned | — | PickingListsBackendApi, UsersBackendApi | (extension-only) |
| PickingListsWarehousesBackendApi | Extension-Only BackendAPI | Planned | — | PickingListsBackendApi, WarehousesBackendApi | (extension-only) |
| ProductAttributesBackendApi | BackendAPI | Planned | — | — | GET,POST /product-attributes&lt;br&gt;GET,PATCH /product-attributes/{id} |
| ProductImageSetsBackendApi | BackendAPI | Planned | — | — | GET /concrete-product-image-sets |
| ProductPackagingUnitsBackendApi | Extension-Only BackendAPI | Planned | — | PickingListsBackendApi | (extension-only) |
| ProductsBackendApi | BackendAPI | Planned | — | — | GET,POST /product-abstract&lt;br&gt;DELETE,GET,PATCH /product-abstract/{id} |
| PushNotificationsBackendApi | BackendAPI | Planned | — | — | GET,POST /push-notification-providers&lt;br&gt;PATCH,DELETE /push-notification-providers/{id}&lt;br&gt;POST /push-notification-subscriptions |
| SalesOrdersBackendApi | BackendAPI | Planned | — | — | GET /sales-orders |
| ServicePointsBackendApi | BackendAPI | Planned | — | — | GET,POST /service-points&lt;br&gt;GET,PATCH /service-points/{id}&lt;br&gt;GET,POST /service-point-addresses&lt;br&gt;PATCH /service-points/{id}/service-point-addresses/{id}&lt;br&gt;GET,POST /service-types&lt;br&gt;GET,PATCH /service-types/{id}&lt;br&gt;GET,POST /services&lt;br&gt;GET,PATCH /services/{id} |
| ShipmentTypesBackendApi | BackendAPI | Planned | — | — | GET,POST /shipment-types&lt;br&gt;GET,PATCH /shipment-types/{id} |
| ShipmentsBackendApi | BackendAPI | Planned | — | — | GET /sales-shipments |
| StoresBackendApi | BackendAPI | Planned | — | — | GET,POST,PATCH /stores |
| UsersBackendApi | BackendAPI | Planned | — | — | GET /users |
| WarehouseOauthBackendApi | BackendAPI | Planned | — | — | POST /warehouse-tokens |
| WarehouseUsersBackendApi | BackendAPI | Planned | — | — | GET,POST /warehouse-user-assignments&lt;br&gt;GET,PATCH,DELETE /warehouse-user-assignments/{id} |
| WarehousesBackendApi | BackendAPI | Planned | — | — | GET /warehouses |
</description>
            <pubDate>Tue, 16 Jun 2026 12:18:37 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/architecture/api-platform/migrate-to-api-platform-status.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/architecture/api-platform/migrate-to-api-platform-status.html</guid>
            
            
        </item>
        
        <item>
            <title>API Platform migration overview</title>
            <description>This document walks through migrating an existing Spryker shop from Glue REST to API Platform. It covers the order to do things in: upgrade the migrated modules, confirm project configuration, batch-migrate, verify, then clean up. Module-by-module mechanics live in the [per-module guide](/docs/dg/dev/upgrade-and-migrate/migrate-to-api-platform.html); project-level setup lives in the [integration guide](/docs/dg/dev/upgrade-and-migrate/integrate-api-platform.html).

## What you&apos;re migrating to

API Platform replaces the internal infrastructure that Glue REST uses to serve API endpoints. Externally, contracts remain backward-compatible — clients keep working. Internally:

- **No more Controllers, Readers, or RestResourceBuilders.** Endpoint behavior is defined in YAML resource schemas plus a Provider (reads) and a Processor (writes).
- **Routing happens via API Platform**, served through `SymfonyFrameworkRouterPlugin`. The `GlueRouterPlugin` continues to serve any modules that haven&apos;t been migrated yet — they coexist by router order.

{% info_block infoBox &quot;Migrated modules no longer rely on Controllers/Readers&quot; %}

Once a module is migrated, its endpoint wiring is the API Platform resource schema and the Provider/Processor pair — not a `*ResourceRoutePlugin` plus a `*Reader`. If you find both, you&apos;re mid-migration; finish the switch (Step 3) before considering that module done.

{% endinfo_block %}

{% info_block warningBox &quot;One client-visible behavior change: the Accept header&quot; %}

Backward compatibility has one exception. Legacy Glue REST accepted requests that omitted the `Accept` header (or sent `*/*`) and answered with `application/vnd.api+json`. API Platform&apos;s content negotiation does not, so such clients can receive `406 Not Acceptable` or a response in a different format. `spryker/api-platform` **1.15.0+** restores the legacy fallback. See [Requests without an Accept header](/docs/dg/dev/architecture/api-platform/troubleshooting.html#requests-without-an-accept-header-are-rejected-or-return-the-wrong-format) in troubleshooting.

{% endinfo_block %}

## Prerequisites

Before starting the migration, confirm:

- **Symfony Dependency Injection is in place.** See [How to upgrade to Symfony Dependency Injection](/docs/dg/dev/upgrade-and-migrate/upgrade-to-symfony-dependency-injection.html).
- **API Platform is integrated** at the project level (bundles registered, project configuration applied, Symfony container compiled). See [How to integrate API Platform](/docs/dg/dev/upgrade-and-migrate/integrate-api-platform.html).
- **PHP 8.3+ and Symfony 6.4+.**
- **You know which modules you&apos;re migrating.** The [Glue API to API Platform migration status page](/docs/dg/dev/architecture/api-platform/migrate-to-api-platform-status.html) lists every module, its migration status, and its prerequisites.
- **Existing Glue API tests pass** on your current shop before you start changing anything. The cleanest signal that the migration is working is that those tests keep passing through every step.

## Step 1 — Upgrade the migrated modules

A shop already lists its `spryker/*-rest-api` modules in the project `composer.json`, so upgrading them to versions that ship the API Platform schemas is a single pattern update:

```bash
composer update &quot;spryker/*-rest-api&quot; --with-dependencies
```

{% info_block warningBox &quot;Endpoints are NOT yet routed after Step 1&quot; %}

Updating the modules ships the API Platform resource schemas and the new Providers/Processors, but **all traffic still goes through the Glue plugins**. You will not see new behavior at this point — that&apos;s expected. Routing flips in Step 3, after the project configuration is in place and the Glue plugins are removed.

{% endinfo_block %}

## Step 2 — Confirm project configuration

API Platform must be integrated at the project level before any module can be served through it. This is the integration step, not part of the migration itself — follow [How to integrate API Platform](/docs/dg/dev/upgrade-and-migrate/integrate-api-platform.html), which covers:

- Registering the bundles in `config/&lt;Application&gt;/bundles.php`.
- The `spryker_api_platform.php` config (including `excludedPathFragments` for the modules you keep on Glue).
- Wiring the router so `GlueRouterPlugin` and `SymfonyFrameworkRouterPlugin` coexist in the correct order.

For the per-setting `api_platform.php` reference, see [API Platform Configuration](/docs/dg/dev/architecture/api-platform/configuration.html). For authentication and authorization, see [How to integrate API Platform Security](/docs/dg/dev/upgrade-and-migrate/integrate-api-platform-security.html).

## Step 3 — Batch migration (default)

The actual switch from Glue REST to API Platform for a given module is **removing that module&apos;s `*ResourceRoutePlugin` from the project-level dependency provider**. This is the single edit that flips routing.

Edit the dependency provider for the stack the module belongs to:

- Storefront API: `src/Pyz/Glue/GlueStorefrontApiApplication/GlueStorefrontApiApplicationDependencyProvider::getResourcePlugins()`
- Backend API: `src/Pyz/Glue/GlueBackendApiApplication/GlueBackendApiApplicationDependencyProvider::getResourcePlugins()`
- Combined Glue: `src/Pyz/Glue/GlueApplication/GlueApplicationDependencyProvider::getResourceRoutePlugins()`

Remove the line that registers the migrated module&apos;s `*ResourceRoutePlugin`. Once removed, `GlueRouterPlugin` no longer finds a match for that resource and the request falls through to `SymfonyFrameworkRouterPlugin`, which serves it via API Platform.

{% info_block warningBox &quot;Plugin removal is the switch — not excludedPathFragments&quot; %}

`excludedPathFragments` in `spryker_api_platform.php` controls what the schema generator emits. It does **not** flip routing. A module stays on Glue as long as its `*ResourceRoutePlugin` is still registered in the project dependency provider, regardless of what `excludedPathFragments` says.

The `spryker/&lt;module&gt;-rest-api` composer package stays installed after you remove the plugin — it just no longer serves routes.

{% endinfo_block %}

### Module dependency order

Some modules cannot be migrated before their dependencies. The prerequisites for each module are tracked in the **Requires** column of the [migration status page](/docs/dg/dev/architecture/api-platform/migrate-to-api-platform-status.html) — migrate the listed prerequisites first (or in the same batch). For example, `ProductPricesRestApi` requires `ProductsRestApi`, so `ProductsRestApi` must be migrated first.

## Step 4 — Verify

After the plugins are removed and the project configuration is in place, verify each migrated endpoint end-to-end. **Now** all migrated endpoints should work via API Platform.

1. **List API Platform resources** per stack:

   ```bash
   docker/sdk cli glue api:debug --list
   docker/sdk cli GLUE_APPLICATION=GLUE_STOREFRONT glue api:debug --list
   docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:debug --list
   ```

   Expected: every resource belonging to a module you removed a `*ResourceRoutePlugin` for appears here under its corresponding stack. The list shows resource names, not module names.

2. **Run the existing Glue API test suite**. All tests should still pass — both the migrated endpoints (now served via API Platform) and any modules still on Glue:

   ```bash
   docker/sdk testing codecept run
   ```

3. **Confirm OpenAPI is generated** at the root URL of each stack:

   - `https://glue.&lt;your-domain&gt;/`
   - `https://glue-storefront.&lt;your-domain&gt;/`
   - `https://glue-backend.&lt;your-domain&gt;/`

   Each should render the interactive OpenAPI documentation, including the migrated resources.

## Step 5 — Cleanup

Once a module is migrated and Step 4 is green, clean up the remaining Glue artifacts for it.

1. **Remove the module&apos;s `*ResourceRoutePlugin` registration** (done in Step 3) and any project-level Glue overrides for that module under `src/Pyz/Glue/&lt;Module&gt;RestApi/`. Many projects have no such directory; only remove what your project actually added. The migration itself lives in the `spryker/&lt;module&gt;-rest-api` vendor package, which stays installed.

2. **Drop `GlueRouterPlugin`** from the router dependency provider — only when **every** module in the stack is migrated:

   `src/Pyz/Glue/Router/RouterDependencyProvider.php`

   ```php
   protected function getRouterPlugins(): array
   {
       return [
           // new GlueRouterPlugin(), // ← Remove once no modules remain on Glue
           new SymfonyFrameworkRouterPlugin(),
       ];
   }
   ```

3. **Remove Glue-specific tests** that are no longer relevant. Replace with API Platform tests if coverage gaps remain.

The endpoint URLs do not change — the migration is fully backward-compatible — so existing API consumers and integrations keep working without updates.
</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-overview.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/upgrade-and-migrate/migrate-to-api-platform-overview.html</guid>
            
            
        </item>
        
        <item>
            <title>How to integrate API Platform</title>
            <description>This document describes how to integrate API Platform into your Spryker application to enable schema-based API resource generation.

If you&apos;re migrating an existing Spryker shop from the Glue REST stack, read the [API Platform migration overview](/docs/dg/dev/upgrade-and-migrate/migrate-to-api-platform-overview.html) first. This integration guide is one step inside that larger flow.

## Prerequisites

Before integrating API Platform, ensure you have:

- Upgraded to Symfony Dependency Injection as described in [How to upgrade to Symfony Dependency Injection](/docs/dg/dev/upgrade-and-migrate/upgrade-to-symfony-dependency-injection.html)
- PHP 8.3 or higher
- Symfony 6.4 or higher components

## 1. Install the required modules

To integrate API Platform, install the following modules:

```bash
composer require spryker/api-platform:&quot;^1.0.0&quot; --update-with-dependencies
```

{% info_block infoBox &quot;Target versions&quot; %}

For the list of currently migrated modules and their status, see the [Glue API to API Platform migration status page](/docs/dg/dev/architecture/api-platform/migrate-to-api-platform-status.html). Update your `spryker/*-rest-api` modules to a version that ships the endpoints you migrate.

{% endinfo_block %}

## 2. Register bundles

Register the required bundles in each application&apos;s bundle file — `config/Glue/bundles.php`, `config/GlueStorefront/bundles.php`, and `config/GlueBackend/bundles.php` are identical:

```php
&lt;?php

declare(strict_types = 1);

use ApiPlatform\Symfony\Bundle\ApiPlatformBundle;
use Spryker\ApiPlatform\SprykerApiPlatformBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\TwigBundle\TwigBundle;

return [
    FrameworkBundle::class =&gt; [&apos;all&apos; =&gt; true],
    SecurityBundle::class =&gt; [&apos;all&apos; =&gt; true],
    TwigBundle::class =&gt; [&apos;all&apos; =&gt; true],
    ApiPlatformBundle::class =&gt; [&apos;all&apos; =&gt; true],
    SprykerApiPlatformBundle::class =&gt; [&apos;all&apos; =&gt; true],
];
```

{% info_block infoBox &quot;SecurityBundle&quot; %}

The `SecurityBundle` enables authentication and authorization for API Platform resources using Bearer token (JWT) validation and security expressions. For detailed setup including firewall configuration, see [How to integrate API Platform Security](/docs/dg/dev/upgrade-and-migrate/integrate-api-platform-security.html).

{% endinfo_block %}

## 3. Configure API Platform

Create a `spryker_api_platform.php` and an `api_platform.php` file in each application layer where you enable API Platform — `config/Glue/packages/`, `config/GlueStorefront/packages/`, and `config/GlueBackend/packages/`. At minimum, `spryker_api_platform.php` must set the application&apos;s `apiTypes()` — `[&apos;storefront&apos;]` for the Glue and GlueStorefront applications, `[&apos;backend&apos;]` for the GlueBackend application.

For the full reference of both configuration files, every available setting, and links to the released demo-shop configurations, see [API Platform Configuration](/docs/dg/dev/architecture/api-platform/configuration.html).

## 4. Update Router Configuration

Update the router dependency provider for each application where you want to enable API Platform routes.

### For Glue application

`src/Pyz/Glue/Router/RouterDependencyProvider.php`

```php
&lt;?php

declare(strict_types = 1);

namespace Pyz\Glue\Router;

use Spryker\Glue\Router\Plugin\Router\SymfonyFrameworkRouterPlugin;
use Spryker\Glue\GlueApplication\Plugin\Rest\GlueRouterPlugin;
use Spryker\Glue\Router\RouterDependencyProvider as SprykerRouterDependencyProvider;

class RouterDependencyProvider extends SprykerRouterDependencyProvider
{
    /**
     * @return array&lt;\Spryker\Glue\RouterExtension\Dependency\Plugin\RouterPluginInterface&gt;
     */
    protected function getRouterPlugins(): array
    {
        return [
            new GlueRouterPlugin(), // Existing Glue API router
            new SymfonyFrameworkRouterPlugin(), // Add this for API Platform routes
        ];
    }
}
```

{% info_block infoBox &quot;Router order matters&quot; %}

The order of router plugins matters. The `SymfonyFrameworkRouterPlugin` must be added after existing router plugins to ensure the correct routing priority. The `GlueRouterPlugin` should remain the first in the list to handle existing and not yet migrated Glue API endpoints.

When migrating existing Glue API endpoints to API Platform, you need to remove endpoints from the `GlueRouterPlugin` so that the `SymfonyFrameworkRouterPlugin` is used to resolve the route. To remove routes from the `GlueRouterPlugin` update the corresponding `GlueApplicationDependencyProvider`, `GlueBackendApiApplicationDependencyProvider`, or `GlueStorefrontApiApplicationDependencyProvider` and remove the resource route plugin for the route you currently migrate.

{% endinfo_block %}

## 5. Generate API resources

After configuration, generate your API resources from the schema files:

```bash
# Generate resources for all configured API types
docker/sdk cli glue api:generate
```

Target a specific application by prefixing with `GLUE_APPLICATION=GLUE_STOREFRONT` or `GLUE_APPLICATION=GLUE_BACKEND`. For the full set of generation and debug commands (single API type, `--dry-run`, `--validate-only`, schema inspection), see [Resource generation](/docs/dg/dev/architecture/api-platform.html#resource-generation) in the architecture guide.

The generated resources are created in `src/Generated/Api/{ApiType}/`.

## 6. Install assets for the API Platform documentation interface

Install the necessary assets for API Platform to function correctly:

### 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
```

{% info_block warningBox &quot;Required step&quot; %}

The `assets:install` command is required to copy the necessary assets (CSS, JavaScript, images) for the API Platform documentation interface. Without this step, the API documentation UI will not display correctly.

{% endinfo_block %}

## 7. Clear caches

After generation and asset installation, clear application caches:

```bash
# Clear all caches
console cache:clear

# Build Symfony container cache
console container:build
```

## Verification

To verify your integration:

1. **Debug available resources:**

   ```bash
   # List all API resources
   docker/sdk cli glue  api:debug --list

   # Inspect specific resource
   docker/sdk cli glue  api:debug access-tokens --api-type=storefront
   ```

2. **Access API documentation:**
   - Glue: `https://glue.your-domain/`
   - GlueStorefront: `https://glue-storefront.your-domain/`
   - GlueBackend: `https://glue-backend.your-domain/`

   The interactive OpenAPI documentation interface will be displayed at the root URL of each application.

   Depending on the environment of the application (development or production), the documentation interface may be enabled or disabled by default. Currently, it is only enabled in development (docker.dev) environments.
   
   You can enable/disable this interface by configuring the settings in your `api_platform.php` configuration files.

## Next steps

- [API Platform](/docs/dg/dev/architecture/api-platform.html) - Overview and concepts
- [How to integrate API Platform Security](/docs/dg/dev/upgrade-and-migrate/integrate-api-platform-security.html) - Authentication and authorization setup
- [API Platform migration overview](/docs/dg/dev/upgrade-and-migrate/migrate-to-api-platform-overview.html) - End-to-end migration walk-through
- [Enablement](/docs/dg/dev/architecture/api-platform/enablement.html) - Create your first API resource
- [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
- [Native API Platform Resources](/docs/dg/dev/architecture/api-platform/native-api-platform-resources.html) - Using native PHP attributes
</description>
            <pubDate>Tue, 16 Jun 2026 12:18:37 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/upgrade-and-migrate/integrate-api-platform.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/upgrade-and-migrate/integrate-api-platform.html</guid>
            
            
        </item>
        
        <item>
            <title>API Platform Configuration</title>
            <description>&lt;p&gt;This page is the canonical reference for all API Platform configuration in Spryker. API Platform is configured through &lt;strong&gt;two&lt;/strong&gt; PHP config files per application: the native &lt;code&gt;api_platform.php&lt;/code&gt; (API Platform’s own options, with Spryker adaptations for PHP-based configuration and environment control) and Spryker’s &lt;code&gt;spryker_api_platform.php&lt;/code&gt; (which drives schema generation). Both are documented below.&lt;/p&gt;
&lt;h2 id=&quot;configuration-file-locations&quot;&gt;Configuration file locations&lt;/h2&gt;
&lt;p&gt;Each application layer carries two configuration files:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Application&lt;/th&gt;
&lt;th&gt;Native API Platform config&lt;/th&gt;
&lt;th&gt;Spryker generator config&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Glue&lt;/td&gt;
&lt;td&gt;&lt;code&gt;config/Glue/packages/api_platform.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;config/Glue/packages/spryker_api_platform.php&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GlueStorefront&lt;/td&gt;
&lt;td&gt;&lt;code&gt;config/GlueStorefront/packages/api_platform.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;config/GlueStorefront/packages/spryker_api_platform.php&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GlueBackend&lt;/td&gt;
&lt;td&gt;&lt;code&gt;config/GlueBackend/packages/api_platform.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;config/GlueBackend/packages/spryker_api_platform.php&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;api_platform.php&lt;/code&gt;&lt;/strong&gt; configures API Platform itself (Swagger, formats, pagination defaults, resource mapping paths). Documented under &lt;a href=&quot;#native-api-platform-configuration-apiplatformphp&quot;&gt;Native API Platform configuration&lt;/a&gt; below.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;spryker_api_platform.php&lt;/code&gt;&lt;/strong&gt; configures Spryker’s schema generator (which API types an application serves, where schemas are scanned, which modules stay on Glue). Documented under &lt;a href=&quot;#resource-generation-configuration-sprykerapiplatformphp&quot;&gt;Resource generation configuration&lt;/a&gt; below.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;released-configuration-reference&quot;&gt;Released configuration reference&lt;/h2&gt;
&lt;p&gt;The simplest starting point is a real, released configuration. The links below point to the B2B Demo Marketplace at the latest release (&lt;code&gt;release-202604.0&lt;/code&gt;); check newer releases for updates.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Application&lt;/th&gt;
&lt;th&gt;&lt;code&gt;api_platform.php&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;spryker_api_platform.php&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Glue&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-shop/b2b-demo-marketplace/blob/release-202604.0/config/Glue/packages/api_platform.php&quot;&gt;api_platform.php&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-shop/b2b-demo-marketplace/blob/release-202604.0/config/Glue/packages/spryker_api_platform.php&quot;&gt;spryker_api_platform.php&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GlueStorefront&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-shop/b2b-demo-marketplace/blob/release-202604.0/config/GlueStorefront/packages/api_platform.php&quot;&gt;api_platform.php&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-shop/b2b-demo-marketplace/blob/release-202604.0/config/GlueStorefront/packages/spryker_api_platform.php&quot;&gt;spryker_api_platform.php&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GlueBackend&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-shop/b2b-demo-marketplace/blob/release-202604.0/config/GlueBackend/packages/api_platform.php&quot;&gt;api_platform.php&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-shop/b2b-demo-marketplace/blob/release-202604.0/config/GlueBackend/packages/spryker_api_platform.php&quot;&gt;spryker_api_platform.php&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;resource-generation-configuration-sprykerapiplatformphp&quot;&gt;Resource generation configuration (spryker_api_platform.php)&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;spryker_api_platform.php&lt;/code&gt; controls Spryker’s API Platform schema generator — it is separate from the native &lt;code&gt;api_platform.php&lt;/code&gt;. Create one in each application layer where you enable API Platform. The files share the same shape; they differ only in &lt;code&gt;apiTypes()&lt;/code&gt; and in the modules each application still serves via Glue.&lt;/p&gt;
&lt;p&gt;Three settings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;apiTypes()&lt;/code&gt; — the API types this application serves: &lt;code&gt;[&apos;storefront&apos;]&lt;/code&gt; for the Glue and GlueStorefront applications, &lt;code&gt;[&apos;backend&apos;]&lt;/code&gt; for the GlueBackend application.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sourceDirectories()&lt;/code&gt; — where the generator scans for API Platform schemas. Optional; defaults to &lt;code&gt;src/Spryker&lt;/code&gt;, &lt;code&gt;src/SprykerFeature&lt;/code&gt;, and &lt;code&gt;src/Pyz&lt;/code&gt;. Set it only if your schemas live somewhere else.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;excludedPathFragments()&lt;/code&gt; — schema paths the generator skips. Use it to keep a module’s API Platform schemas hidden from the generator (for example, a module the project still serves via Glue). Each entry is matched as a substring of the full schema path.&lt;/li&gt;
&lt;/ul&gt;
&lt;section class=&apos;info-block info-block--warning&apos;&gt;&lt;i class=&apos;info-block__icon icon-warning&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;&lt;div class=&quot;info-block__title&quot;&gt;excludedPathFragments does not switch routing&lt;/div&gt;
&lt;p&gt;&lt;code&gt;excludedPathFragments&lt;/code&gt; controls only what the schema generator emits. It does &lt;strong&gt;not&lt;/strong&gt; flip routing. A module stays on Glue as long as its &lt;code&gt;*ResourceRoutePlugin&lt;/code&gt; is registered in the project dependency provider. See &lt;a href=&quot;/docs/dg/dev/upgrade-and-migrate/migrate-to-api-platform-overview.html#step-3--batch-migration-default&quot;&gt;Step 3 — Batch migration in the migration overview&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;p&gt;&lt;code&gt;config/Glue/packages/spryker_api_platform.php&lt;/code&gt; (storefront example):&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;declare&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;strict_types&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Symfony\Config\SprykerApiPlatformConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;SprykerApiPlatformConfig&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$sprykerApiPlatform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$sprykerApiPlatform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;apiTypes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;storefront&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Keep these modules on the Glue REST stack by hiding their API Platform schemas from the generator.&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$sprykerApiPlatform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;excludedPathFragments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
        &lt;span class=&quot;s1&quot;&gt;&apos;vendor/spryker/customer/src/Spryker/Customer/resources/api/&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s1&quot;&gt;&apos;vendor/spryker/store/src/Spryker/Store/resources/api/&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;s1&quot;&gt;&apos;vendor/spryker/authentication/src/Spryker/Authentication/resources/api/&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;For the GlueBackend application, set &lt;code&gt;apiTypes([&apos;backend&apos;])&lt;/code&gt; and list the modules that application still serves via Glue in &lt;code&gt;excludedPathFragments()&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;native-api-platform-configuration-apiplatformphp&quot;&gt;Native API Platform configuration (api_platform.php)&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;api_platform.php&lt;/code&gt; configures API Platform itself. Spryker ships working defaults, so most projects only adjust a few options. The sections below cover the Spryker-specific adaptations and the settings you are most likely to change.&lt;/p&gt;
&lt;h2 id=&quot;spryker-specific-differences&quot;&gt;Spryker-specific differences&lt;/h2&gt;
&lt;h3 id=&quot;php-configuration-instead-of-yaml&quot;&gt;PHP configuration instead of YAML&lt;/h3&gt;
&lt;p&gt;Spryker uses PHP configuration with Symfony’s type-safe configuration objects instead of YAML files:&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Symfony\Config\ApiPlatformConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;ApiPlatformConfig&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$apiPlatform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Configuration here&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;environment-variable-for-conditional-settings&quot;&gt;Environment variable for conditional settings&lt;/h3&gt;
&lt;p&gt;The configuration function receives an &lt;code&gt;$env&lt;/code&gt; parameter for environment-specific behavior:&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;ApiPlatformConfig&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$apiPlatform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Enable developer tools only in development&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$env&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;dockerdev&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$apiPlatform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;enableSwagger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$apiPlatform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;enableSwaggerUi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$apiPlatform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;enableReDoc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$apiPlatform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;enableDocs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Common environment values: &lt;code&gt;prod&lt;/code&gt;, &lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;dockerdev&lt;/code&gt;, &lt;code&gt;test&lt;/code&gt;&lt;/p&gt;
&lt;h2 id=&quot;configuration-examples&quot;&gt;Configuration examples&lt;/h2&gt;
&lt;h3 id=&quot;disable-swaggerui-in-production&quot;&gt;Disable SwaggerUI in production&lt;/h3&gt;
&lt;p&gt;Spryker shows the SwaggerUI only in development environments by default. This can be configured with:&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$env&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;dockerdev&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$apiPlatform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;enableSwagger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$apiPlatform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;enableSwaggerUi&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$apiPlatform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;enableReDoc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$apiPlatform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;enableDocs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;disable-doctrine-integration&quot;&gt;Disable Doctrine integration&lt;/h3&gt;
&lt;p&gt;Spryker does not use Doctrine with API Platform:&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$apiPlatform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;doctrine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$apiPlatform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;doctrineMongodbOdm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;enabled&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;configure-resource-mapping-paths&quot;&gt;Configure resource mapping paths&lt;/h3&gt;
&lt;p&gt;Specify where API Platform discovers resource classes. By default, only the generated resource directory is configured:&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$apiPlatform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mapping&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;%kernel.project_dir%/src/Generated/Api/Backend&apos;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;add-custom-resource-paths&quot;&gt;Add custom resource paths&lt;/h3&gt;
&lt;p&gt;To use native API Platform resources alongside generated resources, add your directories to the mapping paths:&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$apiPlatform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mapping&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;%kernel.project_dir%/src/Generated/Api/Backend&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;%kernel.project_dir%/src/Pyz/Glue/*/Api/Backend/Resource&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;API Platform scans all configured paths for PHP classes with &lt;code&gt;#[ApiResource]&lt;/code&gt; attributes. Generated and manually created resources coexist without conflict.&lt;/p&gt;
&lt;section class=&apos;info-block info-block--warning&apos;&gt;&lt;i class=&apos;info-block__icon icon-warning&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;&lt;div class=&quot;info-block__title&quot;&gt;Keep the generated path&lt;/div&gt;
&lt;p&gt;Always keep the &lt;code&gt;src/Generated/Api/{ApiType}&lt;/code&gt; path in the list. Removing it disables all YAML-generated resources.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;p&gt;For a complete guide on creating native resources, see &lt;a href=&quot;/docs/dg/dev/architecture/api-platform/native-api-platform-resources.html&quot;&gt;Native API Platform Resources&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;set-pagination-defaults&quot;&gt;Set pagination defaults&lt;/h3&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$apiPlatform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;defaults&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;paginationItemsPerPage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;$apiPlatform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;collection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pagination&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pageParameterName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;page&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;itemsPerPageParameterName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;itemsPerPage&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;These global defaults apply to all resources. Individual resources can override pagination behavior using per-resource options such as &lt;code&gt;paginationEnabled&lt;/code&gt;, &lt;code&gt;paginationItemsPerPage&lt;/code&gt;, &lt;code&gt;paginationMaximumItemsPerPage&lt;/code&gt;, &lt;code&gt;paginationClientEnabled&lt;/code&gt;, and &lt;code&gt;paginationClientItemsPerPage&lt;/code&gt; in their YAML schema files. See &lt;a href=&quot;/docs/dg/dev/architecture/api-platform/resource-schemas.html#pagination&quot;&gt;Resource Schemas — Pagination&lt;/a&gt; for details.&lt;/p&gt;
&lt;h3 id=&quot;configure-supported-formats&quot;&gt;Configure supported formats&lt;/h3&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$apiPlatform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;formats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;jsonapi&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;mime_types&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;application/vnd.api+json&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]);&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$apiPlatform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;formats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;jsonld&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;mime_types&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;application/ld+json&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]);&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$apiPlatform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;formats&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;xml&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;mime_types&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;application/xml&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;configure-security&quot;&gt;Configure security&lt;/h3&gt;
&lt;p&gt;Security is configured in a separate &lt;code&gt;security.php&lt;/code&gt; file. For details, see &lt;a href=&quot;/docs/dg/dev/upgrade-and-migrate/integrate-api-platform-security.html&quot;&gt;How to integrate API Platform Security&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;complete-configuration-reference&quot;&gt;Complete configuration reference&lt;/h2&gt;
&lt;p&gt;For all available configuration options and their details, refer to the &lt;a href=&quot;https://api-platform.com/docs/core/configuration/#symfony-configuration&quot;&gt;API Platform Symfony Configuration documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The PHP method names in &lt;code&gt;ApiPlatformConfig&lt;/code&gt; correspond directly to the YAML keys in the official documentation.&lt;/p&gt;
</description>
            <pubDate>Tue, 16 Jun 2026 12:18:37 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/architecture/api-platform/configuration.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/architecture/api-platform/configuration.html</guid>
            
            
        </item>
        
        <item>
            <title>API Platform</title>
            <description>&lt;p&gt;Spryker’s API Platform integration provides schema-based API resource generation with automatic OpenAPI documentation. This allows you to define your API resources using YAML schemas and automatically generate fully functional API endpoints with validation, pagination, and &lt;a href=&quot;/docs/dg/dev/architecture/api-platform/serialization.html&quot;&gt;serialization&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This document describes the API Platform architecture and how it integrates with Spryker.&lt;/p&gt;
&lt;h2 id=&quot;what-is-api-platform&quot;&gt;What is API Platform&lt;/h2&gt;
&lt;p&gt;API Platform is a framework for building modern APIs based on web standards and best practices. In Spryker, it complements the existing Glue API infrastructure by providing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Schema-based resource generation&lt;/strong&gt;: Define resources in YAML, generate PHP classes automatically&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic OpenAPI documentation&lt;/strong&gt;: Interactive API documentation generated from schemas&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Built-in validation&lt;/strong&gt;: Symfony Validator integration with operation-specific rules&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pagination support&lt;/strong&gt;: Standardized pagination with configurable defaults&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State management&lt;/strong&gt;: Separate providers (read) and processors (write) for clean architecture&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Read more about the API Platform project at &lt;a href=&quot;https://api-platform.com/&quot;&gt;api-platform.com&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;why-spryker-is-moving-to-api-platform&quot;&gt;Why Spryker is moving to API Platform&lt;/h3&gt;
&lt;p&gt;API Platform replaces Spryker-specific patterns for routing, authentication, and resource definition with industry-standard Symfony conventions, automatic OpenAPI schema generation, and a clean separation between resource schema, provider, and validation.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Previous infrastructure&lt;/th&gt;
&lt;th&gt;API Platform&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bootstrap&lt;/td&gt;
&lt;td&gt;Spryker-specific application bootstrap&lt;/td&gt;
&lt;td&gt;Symfony Kernel-based routing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource registration&lt;/td&gt;
&lt;td&gt;Manual plugin registration in &lt;code&gt;GlueApplicationDependencyProvider&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Declarative YAML resource definitions (&lt;code&gt;*.resource.yml&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authentication&lt;/td&gt;
&lt;td&gt;Custom flows per module&lt;/td&gt;
&lt;td&gt;Standard OAuth2 / Symfony Security&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coupling&lt;/td&gt;
&lt;td&gt;Tight coupling between resource and routing logic&lt;/td&gt;
&lt;td&gt;Clean separation: provider + resource schema + validation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testability&lt;/td&gt;
&lt;td&gt;Complex to test and extend&lt;/td&gt;
&lt;td&gt;Symfony-native, testable with standard PHPUnit patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAPI&lt;/td&gt;
&lt;td&gt;Manual / partial&lt;/td&gt;
&lt;td&gt;Automatic OpenAPI schema generation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;architecture-overview&quot;&gt;Architecture overview&lt;/h2&gt;
&lt;h3 id=&quot;resource-generation-workflow&quot;&gt;Resource generation workflow&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-MARKDOWN&quot;&gt;&gt;Schema Files (YAML)
    ↓
Schema Discovery &amp;amp; Validation
    ↓
Multi-layer Schema Merging (Core → Feature → Project → [Code Buckets])
    ↓
Resource Class Generation
    ↓
API Platform Resource (with attributes)
    ↓
API Endpoints
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;core-components&quot;&gt;Core components&lt;/h3&gt;
&lt;h4 id=&quot;schema-files&quot;&gt;1. Schema files&lt;/h4&gt;
&lt;p&gt;Resources are defined in YAML files located in module directories:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-MARKDOWN&quot;&gt;&gt;src/Spryker/{Module}/resources/api/{api-type}/{resource-name}.resources.yml
src/Spryker/{Module}/resources/api/{api-type}/{resource-name}.validation.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example resource schema &lt;code&gt;src/Spryker/{Module}/resources/api/{api-type}/{resource-name}.resources.yml&lt;/code&gt;:&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;resource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Customers&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;shortName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;customers&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Customer&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;backend&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;API&quot;&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Pyz&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Glue&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Customer&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Api&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Backend&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Provider&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;CustomerBackendProvider&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;processor&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Pyz&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Glue&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Customer&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Api&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Backend&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Processor&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\\&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;CustomerBackendProcessor&quot;&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;paginationEnabled&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;operations&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Post&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Get&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;GetCollection&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Patch&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Delete&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Customer&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;address&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;customerReference&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;identifier&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;writable&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Example validation schema &lt;code&gt;src/Spryker/{Module}/resources/api/{api-type}/{resource-name}.validation.yml&lt;/code&gt;:&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;post&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;NotBlank&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;First name is required&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Length&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;min&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;max&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;64&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;minMessage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;First name must be at least 2 characters&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;maxMessage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;First name cannot exceed 64 characters&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;patch&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;constraints&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;Length&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;min&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;max&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;64&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;minMessage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;First name must be at least 2 characters&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;maxMessage&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;First name cannot exceed 64 characters&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h4 id=&quot;generated-resources&quot;&gt;2. Generated resources&lt;/h4&gt;
&lt;p&gt;The generator creates PHP classes with API Platform attributes:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/Generated/Api/Backend/CustomersBackendResource.php&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Generated\Api\Backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ApiPlatform\Metadata\ApiResource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ApiPlatform\Metadata\ApiProperty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Symfony\Component\Validator\Constraints&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Assert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;#[ApiResource(&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;operations&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;GetCollection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Patch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()],&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;shortName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;customers&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;provider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomerBackendProvider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;processor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomerBackendProcessor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomersBackendResource&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#[ApiProperty(identifier: true, writable: false)]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;?string&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$customerReference&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;#[ApiProperty]&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#[Assert\NotBlank(groups: [&apos;customers:create&apos;])]&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;#[Assert\Email(groups: [&apos;customers:create&apos;])]&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;?string&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$email&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Getters, setters, toArray(), fromArray()...&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;h4 id=&quot;state-providers-and-processors&quot;&gt;3. State providers and processors&lt;/h4&gt;
&lt;p&gt;Detailed information about the API-Platform Provider and Resources can be found on the public docs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://api-platform.com/docs/core/state-providers/&quot;&gt;API Platform Providers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://api-platform.com/docs/core/state-processors/&quot;&gt;API Platform Processors&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Provider (read operations):&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomerBackendProvider&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ProviderInterface&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;provide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Operation&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$operation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$uriVariables&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Fetch and return data from your business layer&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$customerResource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Processor (write operations):&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomerBackendProcessor&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ProcessorInterface&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;mixed&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Operation&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$operation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$uriVariables&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[]):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;mixed&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Persist changes through your business layer&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$updatedResource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;api-types&quot;&gt;API types&lt;/h2&gt;
&lt;p&gt;Any of the &lt;a href=&quot;https://docs.spryker.com/docs/integrations/spryker-glue-api/getting-started-with-apis/getting-started-with-apis&quot;&gt;existing APIs&lt;/a&gt; can be extended using API Platform.&lt;/p&gt;
&lt;p&gt;Spryker supports multiple API types for different use cases:&lt;/p&gt;
&lt;h3 id=&quot;glue-api&quot;&gt;Glue API&lt;/h3&gt;
&lt;p&gt;This API is configured to serve the JSON:API format by default, which can be configured per project. Projects migrating their APIs can provide new APIs as well as supporting the existing ones while migrating.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;API Type:&lt;/strong&gt; &lt;code&gt;storefront&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Application:&lt;/strong&gt; Glue&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Base URL:&lt;/strong&gt; &lt;code&gt;http://glue.eu.spryker.local/&lt;/code&gt; - Configurable per project&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use cases:&lt;/strong&gt; Customer-facing APIs, mobile apps, PWAs&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;gluestorefront-api&quot;&gt;GlueStorefront API&lt;/h3&gt;
&lt;p&gt;Thie API is configured to serve the JSON+LD format by default, which can be configured per project.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;API Type:&lt;/strong&gt; &lt;code&gt;storefront&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Application:&lt;/strong&gt; Glue&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Base URL:&lt;/strong&gt; &lt;code&gt;http://glue-storefront.eu.spryker.local/&lt;/code&gt; - Configurable per project&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use cases:&lt;/strong&gt; Customer-facing APIs, mobile apps, PWAs&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;gluebackend-api&quot;&gt;GlueBackend API&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;API Type:&lt;/strong&gt; &lt;code&gt;backend&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Application:&lt;/strong&gt; Zed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Base URL:&lt;/strong&gt; &lt;code&gt;http://glue-backend.eu.spryker.local/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use cases:&lt;/strong&gt; Admin panels, internal tools, ERP integrations&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;merchant-portal-api&quot;&gt;Merchant Portal API&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;API Type:&lt;/strong&gt; &lt;code&gt;merchant-portal&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Application:&lt;/strong&gt; MerchantPortal&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Base URL:&lt;/strong&gt; &lt;code&gt;http://mp.glue.eu.spryker.local/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use cases:&lt;/strong&gt; Marketplace merchant interfaces&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Example:&lt;/strong&gt; &lt;code&gt;/products&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;multi-layer-schema-merging&quot;&gt;Multi-layer schema merging&lt;/h2&gt;
&lt;p&gt;One of the key features is support for multi-layer schema definitions that automatically merge:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Core layer&lt;/strong&gt; (vendor/spryker):&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;resource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Customers&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Feature layer&lt;/strong&gt; (src/SprykerFeature):&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;resource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Customers&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;loyaltyPoints&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;integer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Project layer&lt;/strong&gt; (src/Pyz):&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;resource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Customers&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;required&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;c1&quot;&gt;# Override core&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;customField&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;string&lt;/span&gt;    &lt;span class=&quot;c1&quot;&gt;# Project-specific&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: A single merged resource with all properties, project code-bucket layer taking precedence.&lt;/p&gt;
&lt;h2 id=&quot;integration-with-spryker-architecture&quot;&gt;Integration with Spryker architecture&lt;/h2&gt;
&lt;h3 id=&quot;dependency-injection&quot;&gt;Dependency Injection&lt;/h3&gt;
&lt;p&gt;API Platform fully integrates with Symfony Dependency Injection:&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// config/Zed/ApplicationServices.php&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$services&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;Pyz\\Zed\\&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;../../../src/Pyz/Zed/&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Providers and Processors are automatically discovered and can use constructor injection:&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomerBackendProvider&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ProviderInterface&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__construct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CustomerFacadeInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$customerFacade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CustomerRepositoryInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$customerRepository&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;facade-integration&quot;&gt;Facade integration&lt;/h3&gt;
&lt;p&gt;Resources can leverage existing Spryker facades:&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomerBackendProcessor&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ProcessorInterface&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__construct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;CustomerFacadeInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$customerFacade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;mixed&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Operation&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$operation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;mixed&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$customerTransfer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mapToTransfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customerFacade&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;createCustomer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$customerTransfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mapToResource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$response&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getCustomerTransfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;resource-generation&quot;&gt;Resource generation&lt;/h2&gt;
&lt;h3 id=&quot;console-commands&quot;&gt;Console commands&lt;/h3&gt;
&lt;p&gt;All the following commands can be used with a specific GLUE_APPLICATION by prefixing them with &lt;code&gt;GLUE_APPLICATION=GLUE_BACKEND&lt;/code&gt; environment variable. For example: &lt;code&gt;docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:debug --list&lt;/code&gt;&lt;/p&gt;
&lt;div class=&quot;language-bash 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;c&quot;&gt;# Generate resource classes for all configured API types at once. Usually used during deployment/installation.&lt;/span&gt;
docker/sdk cli glue api:generate

&lt;span class=&quot;c&quot;&gt;# Generate API type specific resource classes. Usually used during development.&lt;/span&gt;
docker/sdk cli glue api:generate backend

&lt;span class=&quot;c&quot;&gt;# Validate schemas only to see if there is any issue in the definitions&lt;/span&gt;
docker/sdk cli glue api:generate &lt;span class=&quot;nt&quot;&gt;--validate-only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;debug-commands&quot;&gt;Debug commands&lt;/h3&gt;
&lt;div class=&quot;language-bash 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;c&quot;&gt;# List all resources to see which ones are defined in the schema files.&lt;/span&gt;
docker/sdk cli glue  api:debug &lt;span class=&quot;nt&quot;&gt;--list&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Inspect specific resource and print details about properties and operations&lt;/span&gt;
docker/sdk cli glue  api:debug customers &lt;span class=&quot;nt&quot;&gt;--api-type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;backend

&lt;span class=&quot;c&quot;&gt;# Show merged schema&lt;/span&gt;
docker/sdk cli glue  api:debug customers &lt;span class=&quot;nt&quot;&gt;--api-type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;backend &lt;span class=&quot;nt&quot;&gt;--show-merged&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Show contributing files for a resource&lt;/span&gt;
docker/sdk cli glue  api:debug customers &lt;span class=&quot;nt&quot;&gt;--api-type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;backend &lt;span class=&quot;nt&quot;&gt;--show-sources&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;features&quot;&gt;Features&lt;/h2&gt;
&lt;h3 id=&quot;automatic-openapi-documentation&quot;&gt;Automatic OpenAPI documentation&lt;/h3&gt;
&lt;p&gt;API Platform generates interactive OpenAPI documentation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Swagger UI at the root URL &lt;code&gt;/&lt;/code&gt; for example &lt;code&gt;http://glue-backend.eu.spryker.local/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can disable this interface in production environments by configuring the settings in your &lt;code&gt;api_platform.php&lt;/code&gt; configuration file. For details, see &lt;a href=&quot;/docs/dg/dev/architecture/api-platform/configuration.html#disable-swaggerui-in-production&quot;&gt;Disable Swagger UI&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;built-in-validation&quot;&gt;Built-in validation&lt;/h3&gt;
&lt;p&gt;Validation rules from &lt;code&gt;*.validation.yml&lt;/code&gt; files are converted to Symfony Validator constraints:&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;post&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;NotBlank&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Email&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Becomes:&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;#[Assert\NotBlank(groups: [&apos;customers:create&apos;])]&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#[Assert\Email(groups: [&apos;customers:create&apos;])]&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;?string&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$email&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;pagination-support&quot;&gt;Pagination support&lt;/h3&gt;
&lt;p&gt;Standardized pagination with query parameters:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-MARKDOWN&quot;&gt;&gt;GET /customers?page=2&amp;amp;itemsPerPage=20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Provider returns &lt;code&gt;PaginatorInterface&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TraversablePaginator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ArrayObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$currentPage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$itemsPerPage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$totalItems&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;operation-specific-behavior&quot;&gt;Operation-specific behavior&lt;/h3&gt;
&lt;p&gt;Define different validation and behavior per operation:&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;operations&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Post&lt;/span&gt;            &lt;span class=&quot;c1&quot;&gt;# Create&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Get&lt;/span&gt;             &lt;span class=&quot;c1&quot;&gt;# Read one&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;GetCollection&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# Read many&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Patch&lt;/span&gt;           &lt;span class=&quot;c1&quot;&gt;# Update&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Delete&lt;/span&gt;          &lt;span class=&quot;c1&quot;&gt;# Delete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Each operation can have specific validation rules and security settings.&lt;/p&gt;
&lt;h3 id=&quot;relationships&quot;&gt;Relationships&lt;/h3&gt;
&lt;p&gt;Include related resources via the &lt;code&gt;?include=&lt;/code&gt; query parameter:&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;includes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;relationshipName&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;addresses&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;targetResource&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CustomersAddresses&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;uriVariableMappings&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;customerReference&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;customerReference&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Request:&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;GET /customers/customer--35?include=addresses
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Response includes both the customer and related addresses in JSON:API format. No provider code changes required - relationships work automatically through decoration.&lt;/p&gt;
&lt;p&gt;For detailed information, see &lt;a href=&quot;/docs/dg/dev/architecture/api-platform/relationships.html&quot;&gt;Relationships&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;sparse-fieldsets&quot;&gt;Sparse fieldsets&lt;/h3&gt;
&lt;p&gt;Request only the attributes you need using the &lt;code&gt;fields&lt;/code&gt; query parameter:&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;GET /stores?fields[stores]=name,locale
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This returns only &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;locale&lt;/code&gt; in the response attributes, reducing payload size. Sparse fieldsets work with relationships too — filter attributes on both the main resource and included resources.&lt;/p&gt;
&lt;p&gt;For detailed information, see &lt;a href=&quot;/docs/dg/dev/architecture/api-platform/sparse-fieldsets.html&quot;&gt;Sparse Fieldsets&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;performance&quot;&gt;Performance&lt;/h2&gt;
&lt;h3 id=&quot;cache-warming&quot;&gt;Cache warming&lt;/h3&gt;
&lt;p&gt;Pre-generate resources during deployment:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker/sdk cli glue  api:generate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker/sdk cli glue  cache:warmup
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;property-level-access-control&quot;&gt;Property-level access control&lt;/h3&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;properties&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;writable&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;c1&quot;&gt;# Can be written&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;readable&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Not in responses&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;comparison-with-glue-api&quot;&gt;Comparison with Glue API&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;API Platform&lt;/th&gt;
&lt;th&gt;Glue API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Definition&lt;/td&gt;
&lt;td&gt;Schema-based (YAML)&lt;/td&gt;
&lt;td&gt;Code-based (PHP)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documentation&lt;/td&gt;
&lt;td&gt;Auto-generated OpenAPI&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validation&lt;/td&gt;
&lt;td&gt;Declarative&lt;/td&gt;
&lt;td&gt;Programmatic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standards&lt;/td&gt;
&lt;td&gt;JSON-LD, Hydra&lt;/td&gt;
&lt;td&gt;JSON API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning curve&lt;/td&gt;
&lt;td&gt;Lower&lt;/td&gt;
&lt;td&gt;Higher&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flexibility&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Very high&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use cases&lt;/td&gt;
&lt;td&gt;Standard CRUD&lt;/td&gt;
&lt;td&gt;Complex business logic&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Both can coexist in the same application. For further migration guidance, see &lt;a href=&quot;/docs/dg/dev/upgrade-and-migrate/migrate-to-api-platform.html&quot;&gt;How to migrate to API Platform&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;related-documentation&quot;&gt;Related documentation&lt;/h2&gt;
&lt;p&gt;For detailed implementation guides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/upgrade-and-migrate/integrate-api-platform.html&quot;&gt;How to integrate API Platform&lt;/a&gt; - Setup and configuration&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/upgrade-and-migrate/integrate-api-platform-security.html&quot;&gt;How to integrate API Platform Security&lt;/a&gt; - Authentication and authorization setup&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/upgrade-and-migrate/migrate-to-api-platform.html&quot;&gt;How to migrate to API Platform&lt;/a&gt; - Migrate endpoints from Glue API&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/architecture/api-platform/configuration.html&quot;&gt;API Platform Configuration&lt;/a&gt; - Configure API Platform settings&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/architecture/api-platform/security.html&quot;&gt;Security&lt;/a&gt; - Authentication and authorization&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/architecture/api-platform/enablement.html&quot;&gt;API Platform Enablement&lt;/a&gt; - Creating your first resource&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/architecture/api-platform/resource-schemas.html&quot;&gt;Resource Schemas&lt;/a&gt; - Resource Schemas&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/architecture/api-platform/validation-schemas.html&quot;&gt;Validation Schemas&lt;/a&gt; - Validation Schemas&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/architecture/api-platform/native-api-platform-resources.html&quot;&gt;Native API Platform Resources&lt;/a&gt; - Using native PHP attributes&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/architecture/api-platform/code-buckets.html&quot;&gt;CodeBucket Support&lt;/a&gt; - Region-specific resources&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/architecture/api-platform/sparse-fieldsets.html&quot;&gt;Sparse Fieldsets&lt;/a&gt; - Request only needed attributes&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/architecture/api-platform/serialization.html&quot;&gt;Serialization&lt;/a&gt; - How requests and responses are serialized&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/architecture/api-platform/troubleshooting.html&quot;&gt;Troubleshooting API Platform&lt;/a&gt; - Common issues&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/upgrade-and-migrate/integrate-api-platform.html&quot;&gt;How to integrate API Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/upgrade-and-migrate/integrate-api-platform-security.html&quot;&gt;How to integrate API Platform Security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/architecture/api-platform/native-api-platform-resources.html&quot;&gt;Native API Platform Resources&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/architecture/api-platform/code-buckets.html&quot;&gt;CodeBucket Support in API Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://api-platform.com/docs/&quot;&gt;API Platform official documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
            <pubDate>Tue, 16 Jun 2026 12:18:37 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/architecture/api-platform.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/architecture/api-platform.html</guid>
            
            
        </item>
        
    </channel>
</rss>
