<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Spryker Documentation</title>
        <description>Spryker documentation center.</description>
        <link>https://docs.spryker.com/</link>
        <atom:link href="https://docs.spryker.com/feed.xml" rel="self" type="application/rss+xml"/>
        <lastBuildDate>Thu, 09 Jul 2026 07:32:30 +0000</lastBuildDate>
        <generator>Jekyll v4.2.2</generator>
        
        
        <item>
            <title></title>
            <description># Integrate the Sass module-system migration into the Demo Shop

This guide explains how to integrate the **Sass module-system migration** into the **Spryker Demo Shop**.

The update replaces:

- the legacy Sass build flow based on `@import` and implicit global injections (historically through `sass-resources-loader`)
- the **Sass module system** that uses `@use` and `@forward`, aligned with the **ShopUi vendor builder**

Run the migration by using the provided codemod script (`migrate-sass.mjs`), which performs most of the required changes automatically.

## Scope

This guide covers:

- running the migration script and its phases
- frontend builder file changes (bootstrap and project settings override)
- TypeScript `tsconfig` path aliases required by the new style imports
- Sass module-system rules that you must follow after the migration
- verification and troubleshooting

## Audience

Use this guide if you maintain an **existing, customized** B2B Demo Shop with local SCSS overrides.

## Prerequisites

Before you start, make sure that you have:

- a clean Git working tree (commit or stash your changes)
- Node.js installed and available because the migration is a Node.js script
- updated Composer dependencies that include the ShopUi builder and module-system styles

{% info_block infoBox &quot;Info&quot; %}
The migration script validates the prerequisites during its preflight phase and stops if your installed vendor packages are not compatible.
{% endinfo_block %}

## 1. Update Composer dependencies

The migration requires a ShopUi version that provides:

- the frontend builder entry point: `vendor/.../ShopUi/builder/build.mjs`
- the module-system shared hub: `vendor/.../ShopUi/Theme/default/styles/shared.scss`, which contains `@forward`

Update your project dependencies by updating the relevant Spryker packages and then running `composer update` or `composer install`, as appropriate.

{% info_block warningBox &quot;Warning&quot; %}
Do not run the codemod before updating the vendor packages. The migration relies on the new builder and the new vendor styles.
{% endinfo_block %}

## 2. Run the migration script

Run the migration from the Demo Shop repository root:

```bash
node migrate-sass.mjs
```

The script is **idempotent**. You can rerun it after manual fixes or partial runs.

### 2.1 Run a dry run

To preview the changes without writing any files:

```bash
node migrate-sass.mjs --dry-run
```

### 2.2 Run only specific phases

To limit the migration scope:

```bash
node migrate-sass.mjs --phase=frontend,tsconfig,hub,components
```

### 2.3 Skip verification

If you want to apply the structural changes first and verify them later, run:

```bash
node migrate-sass.mjs --skip-verify
```

### 2.4 Run in modernize-only mode

If your project has already been migrated structurally and you only want to fix Sass deprecations (for example, global built-in functions or invalid selectors), run:

```bash
node migrate-sass.mjs --modernize-only
```

## 3. Install updated frontend dependencies

The frontend phase updates the `package.json` scripts and development dependencies.

After you run the script, install the Node.js dependencies:

```bash
npm install
```

{% info_block infoBox &quot;Info&quot; %}
The verification phase uses `sass-embedded`. If you skip `npm install`, the verification fails even if your SCSS changes are correct.
{% endinfo_block %}

## 4. Review the migration changes

### 4.1 Frontend builder integration

The script removes the legacy frontend build files and creates a new builder bootstrap.

After the migration, the following files are available:

- `frontend/build.mjs` — Builder bootstrap that calls the vendor builder `run()`.
- `frontend/settings.override.mjs` — Project-level builder settings override.
- `frontend/configs/asset-blacklist.mjs` — Project asset compilation blacklist.
- `frontend/libs/stylelint.mjs` — Stylelint runner.
- `frontend/libs/design-tokens.mjs` — Optional design token build hook.

The `package.json` scripts are updated to call `node ./frontend/build.mjs`, including `yves`, `yves:watch`, and `yves:production`.

The script also replaces `sass` with `sass-embedded` in `devDependencies` and removes `sass-resources-loader`, if it is present.

{% info_block infoBox &quot;Info&quot; %}
The legacy webpack-based image optimization integration described in [Implement Image Optimization](/docs/dg/dev/frontend-development/latest/yves/implement-image-optimization.html) has been removed from the Demo Shop builder flow.

This behavior is intentional:

- Spryker projects typically **serve images from third-party resources**, such as CDNs, DAM systems, or other external media services, instead of relying on build-time image processing in the storefront repository.
- In this setup, image transformations such as compression, resizing, and format conversion are handled closer to the delivery edge, while the storefront build remains focused on compiling CSS and JavaScript assets consistently.

If you still require build-time optimization for locally stored images, reintroduce it as a project-specific build step, such as a dedicated builder hook, so that it remains explicit and maintainable.
{% endinfo_block %}

#### Migrate component exclusions from `$setting-import-blacklist` to `asset-blacklist.mjs`

If your project previously excluded component styles by using the legacy Sass blacklist variable, migrate that configuration to the new builder configuration.

- **Legacy approach:** Add component patterns to the `$setting-import-blacklist` Sass variable, which is used by the legacy `helper-import` mechanism. This excludes **only SCSS** from the build. Component JavaScript can still be discovered and bundled.
- **Current approach:** Use `frontend/configs/asset-blacklist.mjs` to exclude **both SCSS and JavaScript** entry points from the Yves build.

**To migrate your configuration:**

1. Locate your `$setting-import-blacklist` configuration and any project documentation about excluded components.
2. Copy the same component patterns into `frontend/configs/asset-blacklist.mjs` as glob patterns.
3. If an excluded component also contains JavaScript entry points, exclude those as well. This is the primary behavioral difference from the legacy approach.

**Why this matters**

With the legacy approach, you could exclude a component&apos;s styles while still shipping its JavaScript. With `asset-blacklist.mjs`, you can exclude both styles and scripts consistently during the build.

{% info_block infoBox &quot;Info&quot; %}
The ShopUi frontend builder evaluates `asset-blacklist.mjs` by using the source roots configured in `frontend/settings.override.mjs`.
{% endinfo_block %}

{% info_block warningBox &quot;Warning&quot; %}
If you maintain custom frontend build logic under `frontend/`, review `frontend/settings.override.mjs` carefully. The migration overwrites only known legacy files. It does not remove arbitrary project-specific scripts.
{% endinfo_block %}

### 4.2 TypeScript path aliases (`tsconfig`)

The script ensures that the following path aliases exist in your Yves TypeScript configuration:

- `ShopUi/*` resolves to the vendor ShopUi theme.
- `src/ShopUi/*` resolves to the project ShopUi theme.

These aliases are required because the migrated SCSS uses imports such as:

- `@use &apos;ShopUi/styles/...&apos;;` for vendor modules
- `@use &apos;src/ShopUi/styles/...&apos;;` for project modules

## 5. Follow the Sass module-system rules after the migration

### 5.1 Treat `shared.scss` as a forward-only hub

The migration rewrites the project hub at `src/Pyz/Yves/ShopUi/Theme/default/styles/shared.scss` as a **forward-only hub**.

Follow these rules:

- Do not emit CSS from `shared.scss`.
- Keep the declarations in the following order:
  1. Settings
  2. Helpers

**Reason:** Components `@use` the shared hub. If `shared.scss` emits CSS, each component that loads it emits duplicate CSS.

### 5.2 Configure vendor settings through `src/ShopUi/...`

Project settings files under `src/Pyz/Yves/ShopUi/Theme/default/styles/settings/` configure vendor settings by using:

```scss
@forward &apos;ShopUi/styles/settings/&lt;name&gt;&apos; with (...);
```

After the migration:

- Do not load vendor settings directly by using `@use &apos;ShopUi/styles/settings/&lt;name&gt;&apos;` from project code.
- Always load the project wrapper instead:

```scss
@use &apos;src/ShopUi/styles/settings/&lt;name&gt;&apos; as *;
```

**Reason:** Sass modules are loaded only once. If a vendor settings module is loaded before it is configured with `with (...)`, you cannot configure it later, and the compilation fails.

### 5.3 Use the standard component SCSS entry point

For components, templates, and views, the migration prepends:

```scss
@use &apos;src/ShopUi/styles/shared&apos; as *;
```

This ensures that your component has access to the configured settings and the shared helper mixins and functions.

### 5.4 Move top-level `@include` statements into `style.scss` when required

If a component SCSS file contains trailing top-level `@include` statements (for example, a legacy pattern in which a mixin emits the final CSS), the script can:

- Move the `@include` statements into a sibling `style.scss` file.
- Update the component&apos;s `.ts` file to import `./style.scss`.

This approach keeps the base SCSS file reusable as a module while `style.scss` serves as the CSS entry point.

### 5.5 Modernize deprecated built-in functions and invalid selectors

The script modernizes deprecated Sass patterns, including:

- deprecated global built-in functions, for example:
  - `map-get(...)` → `map.get(...)`, and adds `@use &apos;sass:map&apos;;`
- invalid selectors with leading combinators, for example:
  - `+ &amp; { ... }` → `&amp; + &amp; { ... }`

{% info_block warningBox &quot;Warning&quot; %}
Rewriting `+ &amp;` to `&amp; + &amp;` can activate CSS rules that browsers previously ignored because the original selector was invalid. Verify the affected components visually after the migration.
{% endinfo_block %}

### 5.6 Replace `readyCallback()` with `init()`

The `readyCallback()` method has been removed from the core and replaced with `init()`. After the migration, check whether any of your project-level scripts define or call this method and update them accordingly:

- Replace `readyCallback()` with `init()`.
- Replace `super.readyCallback();` with `super.init();`.

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

If your project-level scripts still use `readyCallback()` or `super.readyCallback();`, they do not run after the migration because the method no longer exists in the core.

{% endinfo_block %}

## 6. Verify the migration

### 6.1 Run a frontend build

Run the standard Yves build for your project:

```bash
npm run yves
```

Also verify the production build:

```bash
npm run yves:production
```

### 6.2 Verify SCSS compilation

By default, `node migrate-sass.mjs` ends with a verification phase that compiles the migrated stylesheets by using `sass-embedded` and the same alias resolution rules.

If you ran the script with `--skip-verify`, rerun it later without that flag to perform the verification.

## 7. Troubleshooting

### 7.1 Preflight error: ShopUi builder is missing

**Symptom:** The script fails during the preflight phase and reports that the ShopUi builder entry does not exist.

**Cause:** Your vendor packages do not include a ShopUi version that provides the frontend builder.

**Fix:** Update your Composer dependencies so that ShopUi provides `builder/build.mjs`, then rerun the script.

### 7.2 Preflight error: Vendor `shared.scss` is not `@forward`-based

**Symptom:** The script reports that the vendor `styles/shared.scss` file is not based on `@forward`.

**Cause:** Your ShopUi styles still use the legacy `@import` approach.

**Fix:** Update your vendor dependencies to a version that supports the Sass module system.

### 7.3 Sass error: &quot;This module was already loaded, so it cannot be configured using with&quot;

**Symptom:** Sass compilation fails with an error similar to the following:

- `This module was already loaded, so it cannot be configured using with`.

**Cause:** A vendor settings module was loaded directly (for example, `@use &apos;ShopUi/styles/settings/colors&apos;`) before the project wrapper configured it.

**Fix:**

- Replace direct vendor settings imports with:

  ```scss
  @use &apos;src/ShopUi/styles/settings/&lt;name&gt;&apos; as *;
  ```

- Ensure that `src/ShopUi/styles/shared` is loaded before any vendor helper or utility module that depends on settings.

### 7.4 Sass error: Missing TypeScript alias or unresolved import

**Symptom:** Sass cannot resolve imports such as `src/ShopUi/...` or `&lt;Module&gt;/...`.

**Cause:** The required `tsconfig` path aliases are missing.

**Fix:** Rerun the script with `--phase=tsconfig`, then commit the updated `tsconfig.json` file.

### 7.5 Visual regressions after the combinator fix

**Symptom:** Visual changes appear in components whose SCSS contains selectors such as `+ &amp;`.

**Cause:** The migration rewrites `+ &amp;` to `&amp; + &amp;`, which changes previously invalid CSS into valid CSS.

**Fix:** Review the affected selectors and confirm that they produce the intended behavior.

{% info_block infoBox &quot;Info&quot; %}
If you need to rerun only the deprecation fixes across the entire project, use:

```bash
node migrate-sass.mjs --modernize-only
```

{% endinfo_block %}

## 8. Commit strategy

To simplify code reviews, apply and commit the migration in separate commits:

1. Update the Composer dependencies (ShopUi builder and styles).
2. Commit the frontend builder and `package.json` changes.
3. Commit the `tsconfig` alias changes.
4. Commit the ShopUi styles hub migration.
5. Commit the component, template, and view migration.
6. Commit the manual fixes and visual regression adjustments.</description>
            <pubDate>Thu, 09 Jul 2026 07:31:56 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/upgrade-and-migrate/sass-migration-and-yves-fe-builder-move-to-the-demo-shop.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/upgrade-and-migrate/sass-migration-and-yves-fe-builder-move-to-the-demo-shop.html</guid>
            
            
        </item>
        
        <item>
            <title>Add variables in the Parameter Store</title>
            <description>&lt;p&gt;Variables, such as parameters and secrets, are used for multiple purposes, like storing mail server details or providing Composer authentication details to the build and deploy process securely.&lt;/p&gt;
&lt;section class=&apos;info-block &apos;&gt;&lt;i class=&apos;info-block__icon icon-info&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;
&lt;p&gt;This feature is part of a gradual rollout and will be available to everyone eventually. We will notify your team once your project is onboarded.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;p&gt;For complex or system-critical changes to variables, we recommend consulting with our support to prevent unexpected issues.&lt;/p&gt;
&lt;h2 id=&quot;customer-owned-and-spryker-owned-variables&quot;&gt;Customer-owned and Spryker-owned variables&lt;/h2&gt;
&lt;p&gt;There are two types of environment variables: customer-owned and Spryker-owned.&lt;/p&gt;
&lt;p&gt;Spryker-owned variables are managed and can be updated only with the help of Spryker Cloud or support teams. To request a variable change, in &lt;a href=&quot;https://support.spryker.com/&quot;&gt;Support Portal&lt;/a&gt;, create the following request: Infrastructure Change Request &amp;gt; Change to existing parameter store variable.&lt;/p&gt;
&lt;p&gt;Customer-owned variables are created and managed by you or implementation partner. You have full control over these variables and can add or edit them according to your needs. Changes to these variables don’t automatically propagate into a running environment. To apply changes made to your environment variables, you need to run an ECS-updater-* pipeline to bring them to the containers or run a full redeploy. You can do this whole process without creating support tickets.&lt;/p&gt;
&lt;h3 id=&quot;customer-owned-variables-with-limited-access&quot;&gt;Customer-owned variables with limited access&lt;/h3&gt;
&lt;p&gt;The following customer-owned variables can be updated only with the help of Spryker Cloud or support teams:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;/{project}/{environment}/secret/scheduler/limited/{variable_name}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;/{project}/{environment}/secret/pipeline/limited/{variable_name}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;/{project}/{environment}/secret/common/limited/{variable_name}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;/{project}/{environment}/secret/app/limited/{variable_name}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;/{project}/{environment}/config/scheduler/limited/{variable_name}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;/{project}/{environment}/config/pipeline/limited/{variable_name}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;/{project}/{environment}/config/common/limited/{variable_name}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;/{project}/{environment}/config/app/limited/{variable_name}&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;naming-convention-for-variables&quot;&gt;Naming convention for variables&lt;/h2&gt;
&lt;p&gt;Variables must follow this naming convention: &lt;code&gt;/{project}/{environment}/{type}/{bucket}/{grant}/{variable_name}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Placeholder description:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;type&lt;/code&gt;: defines the type of a variable. Possible values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;config&lt;/code&gt;: parameter&lt;/li&gt;
&lt;li&gt;&lt;code&gt;secret&lt;/code&gt;: secret&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;bucket&lt;/code&gt;: defines what services a variable is used for. Possible values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;common&lt;/code&gt;: used by all the buckets.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app&lt;/code&gt;: used only by application services.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scheduler&lt;/code&gt;: used by the scheduler.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;grant&lt;/code&gt;: Defines access permissions to variables. Possible values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public&lt;/code&gt;: readable and writable&lt;/li&gt;
&lt;li&gt;&lt;code&gt;limited&lt;/code&gt;: readable&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Path examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;/fashion_club_store/staging/config/common/limited/composer_pass&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;/deans_jeans/prod/config/app/public/mail_host&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;reserved-variables&quot;&gt;Reserved variables&lt;/h3&gt;
&lt;p&gt;Reserved variables are reserved in Spryker for dedicated functions. These names can’t be used to create more variables. If you’re already using reserved variables in your code, you need to change their names to avoid any service issues.&lt;/p&gt;
&lt;p&gt;Reserved variables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SPRYKER_*&lt;/code&gt;: Every variable name with this prefix&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ALLOWED_IP&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BLACKFIRE_AGENT_SOCKET&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BLACKFIRE_SERVER_ID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BLACKFIRE_SERVER_TOKEN&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DATA_IMPORT_S3_BUCKET&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DATA_IMPORT_S3_KEY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DATA_IMPORT_S3_SECRET&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DUMMY_INIT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ENABLE_NRI_ECS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JAVA_OPTS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JENKINS_URL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NEWRELIC_APPNAME&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NEWRELIC_ENABLED&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NEWRELIC_LICENSE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NRIA_CUSTOM_ATTRIBUTES&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NRIA_LICENSE_KEY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NRIA_OVERRIDE_HOST_ROOT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NRIA_PASSTHROUGH_ENVIRONMENT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NRIA_VERBOSE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ONEAGENT_INSTALLER_DOWNLOAD_TOKEN&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ONEAGENT_INSTALLER_SCRIPT_URL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RABBITMQ_DEFAULT_PASS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RABBITMQ_DEFAULT_USER&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RABBITMQ_DEFAULT_VHOST&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RABBITMQ_ENDPOINT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RABBITMQ_EXCHANGE_REGEXES&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RABBITMQ_INTEGRATIONS_INTERVAL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RABBITMQ_NODENAME&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RABBITMQ_PASSWORD&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RABBITMQ_PORT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RABBITMQ_QUEUES_REGEXES&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RABBITMQ_USE_SSL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RABBITMQ_USERNAME&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TIDEWAYS_APIKEY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TIDEWAYS_CLI_ENABLED&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TIDEWAYS_DAEMON_URI&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TIDEWAYS_ENVIRONMENT&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;variable-path-hierarchy&quot;&gt;Variable path hierarchy&lt;/h2&gt;
&lt;p&gt;Path hierarchy is needed to cover the cases when several variables with the same name are declared. If several variables with the same name are declared, the variable with a higher priority applies. The following rules define the priority of variables:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;For any &lt;code&gt;type&lt;/code&gt; and &lt;code&gt;bucket&lt;/code&gt;, the priority is &lt;code&gt;public&lt;/code&gt; &amp;gt; &lt;code&gt;limited&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Foy any &lt;code&gt;bucket&lt;/code&gt;, the priority is &lt;code&gt;bucket&lt;/code&gt; &amp;gt; &lt;code&gt;common&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For any variable with the same name, the priority is &lt;code&gt;secret&lt;/code&gt; &amp;gt; &lt;code&gt;config&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The following variables are arranged from lower to higher priority:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;/{project}/{environment}/config/common/limited/{variable_name}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;/{project}/{environment}/config/common/public/{variable_name}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;/{project}/{environment}/config/{app | scheduler}/limited/{variable_name}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;/{project}/{environment}/config/{app | scheduler}/public/{variable_name}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;/{project}/{environment}/secret/common/limited/{variable_name}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;/{project}/{environment}/secret/common/public/{variable_name}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;/{project}/{environment}/secret/{app | scheduler}/limited/{variable_name}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;/{project}/{environment}/secret/{app | scheduler}/public/{variable_name}&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;add-variables&quot;&gt;Add variables&lt;/h2&gt;
&lt;p&gt;The following sections describe how to add parameters and secrets for different resources.&lt;/p&gt;
&lt;h3 id=&quot;add-parameters-to-all-resource-types&quot;&gt;Add parameters to all resource types&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;In the AWS Management Console, go to &lt;strong&gt;Services &lt;span aria-label=&quot;and then&quot;&gt;&amp;gt;&lt;/span&gt; Parameter Store&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;My parameters&lt;/strong&gt; pane, click &lt;strong&gt;Create parameter&lt;/strong&gt;.
This opens the &lt;strong&gt;Create parameter&lt;/strong&gt; page.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Name&lt;/strong&gt;, enter &lt;code&gt;/{project}/{environment}/config/common/public/{variable_name}&lt;/code&gt;.
Make sure to replace the placeholders based on your requirements.&lt;/li&gt;
&lt;li&gt;Optional: For &lt;strong&gt;Description&lt;/strong&gt;, enter a description of the variable. This may be a note about what this variable is used for.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Type&lt;/strong&gt;, select a type of the variable based on your requirements.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Value&lt;/strong&gt;, enter the value of the variable.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create parameter&lt;/strong&gt;.
This opens the &lt;strong&gt;Parameter Store&lt;/strong&gt; page with a success message displayed.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Services&lt;/strong&gt; &lt;span aria-label=&quot;and then&quot;&gt;&amp;gt;&lt;/span&gt; &lt;strong&gt;CodePipeline&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;On the &lt;strong&gt;Pipelines&lt;/strong&gt; page, select the &lt;strong&gt;NORMAL_Deploy_Spryker_{project}-{environment}&lt;/strong&gt; pipeline.&lt;/li&gt;
&lt;li&gt;On the pipeline’s page, click &lt;strong&gt;Release change&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Release change&lt;/strong&gt; window, click &lt;strong&gt;Release&lt;/strong&gt;.
After the pipeline finishes running, the variable gets available for your application.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;add-secrets-to-all-resource-types&quot;&gt;Add secrets to all resource types&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;In the AWS Management Console, go to &lt;strong&gt;Services &lt;span aria-label=&quot;and then&quot;&gt;&amp;gt;&lt;/span&gt; Parameter Store&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;My parameters&lt;/strong&gt; pane, click &lt;strong&gt;Create parameter&lt;/strong&gt;.
This opens the &lt;strong&gt;Create parameter&lt;/strong&gt; page.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Name&lt;/strong&gt;, enter &lt;code&gt;/{project}/{environment}/secret/common/public/{variable_name}&lt;/code&gt;.
Make sure to replace the placeholders based on your requirements.&lt;/li&gt;
&lt;li&gt;Optional: For &lt;strong&gt;Description&lt;/strong&gt;, enter a description of the variable. This may be a note about what this variable is used for.
This may be a note about what this variable is used for.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Type&lt;/strong&gt;, select &lt;strong&gt;SecureString&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Value&lt;/strong&gt;, enter the value of the variable.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create parameter&lt;/strong&gt;.
This opens the &lt;strong&gt;Parameter Store&lt;/strong&gt; page with a success message displayed.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Services &lt;span aria-label=&quot;and then&quot;&gt;&amp;gt;&lt;/span&gt; CodePipeline&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;On the &lt;strong&gt;Pipelines&lt;/strong&gt; page, select the &lt;strong&gt;NORMAL_Deploy_Spryker_{project}-{environment}&lt;/strong&gt; pipeline.&lt;/li&gt;
&lt;li&gt;On the pipeline’s page, click &lt;strong&gt;Release change&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Release change&lt;/strong&gt; window, click &lt;strong&gt;Release&lt;/strong&gt;.
After the pipeline finishes running, the variable gets available for your application.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;add-parameters-to-ecs-applications&quot;&gt;Add parameters to ECS applications&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;In the AWS Management Console, go to &lt;strong&gt;Services &lt;span aria-label=&quot;and then&quot;&gt;&amp;gt;&lt;/span&gt; Parameter Store&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;My parameters&lt;/strong&gt; pane, click &lt;strong&gt;Create parameter&lt;/strong&gt;.
This opens the &lt;strong&gt;Create parameter&lt;/strong&gt; page.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Name&lt;/strong&gt;, enter &lt;code&gt;/{project}/{environment}/config/app/public/{variable_name}&lt;/code&gt;.
Make sure to replace the placeholders based on your requirements.&lt;/li&gt;
&lt;li&gt;Optional: For &lt;strong&gt;Description&lt;/strong&gt;, enter a description of the variable. This may be a note about what this variable is used for.
This may be a note about what this variable is used for.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Type&lt;/strong&gt;, select a type of the variable based on your requirements.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Value&lt;/strong&gt;, enter the value of the variable.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create parameter&lt;/strong&gt;.
This opens the &lt;strong&gt;Parameter Store&lt;/strong&gt; page with a success message displayed.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Services &lt;span aria-label=&quot;and then&quot;&gt;&amp;gt;&lt;/span&gt; CodePipeline&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;On the &lt;strong&gt;Pipelines&lt;/strong&gt; page, select the &lt;strong&gt;ECS-updater-{project}-{environment}&lt;/strong&gt; pipeline.&lt;/li&gt;
&lt;li&gt;On the pipeline’s page, click &lt;strong&gt;Release change&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Release change&lt;/strong&gt; window, click &lt;strong&gt;Release&lt;/strong&gt;.
After the pipeline finishes running, the variable gets available for your application.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;adding-secrets-to-ecs-applications&quot;&gt;Adding secrets to ECS applications&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;In the AWS Management Console, go to &lt;strong&gt;Services &lt;span aria-label=&quot;and then&quot;&gt;&amp;gt;&lt;/span&gt; Parameter Store&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;My parameters&lt;/strong&gt; pane, click &lt;strong&gt;Create parameter&lt;/strong&gt;.
This opens the &lt;strong&gt;Create parameter&lt;/strong&gt; page.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Name&lt;/strong&gt;, enter &lt;code&gt;/{project}/{environment}/secret/app/public/{variable_name}&lt;/code&gt;.
Make sure to replace the placeholders based on your requirements.&lt;/li&gt;
&lt;li&gt;Optional: For &lt;strong&gt;Description&lt;/strong&gt;, enter a description of the variable. This may be a note about what this variable is used for.
This may be a note about what this variable is used for.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Type&lt;/strong&gt;, select &lt;strong&gt;SecureString&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Value&lt;/strong&gt;, enter the value of the variable.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create parameter&lt;/strong&gt;.
This opens the &lt;strong&gt;Parameter Store&lt;/strong&gt; page with a success message displayed.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Services&lt;/strong&gt; &lt;span aria-label=&quot;and then&quot;&gt;&amp;gt;&lt;/span&gt; &lt;strong&gt;CodePipeline&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;On the &lt;strong&gt;Pipelines&lt;/strong&gt; page, select the &lt;strong&gt;ECS-updater-{project}-{environment}&lt;/strong&gt; pipeline.&lt;/li&gt;
&lt;li&gt;On the pipeline’s page, click &lt;strong&gt;Release change&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Release change&lt;/strong&gt; window, click &lt;strong&gt;Release&lt;/strong&gt;.
After the pipeline finishes running, the variable gets available for your application.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;add-parameters-and-secrets-to-scheduler&quot;&gt;Add parameters and secrets to Scheduler&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;In the AWS Management Console, go to &lt;strong&gt;Services &lt;span aria-label=&quot;and then&quot;&gt;&amp;gt;&lt;/span&gt; Parameter Store&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;My parameters&lt;/strong&gt; pane, click &lt;strong&gt;Create parameter&lt;/strong&gt;.
This opens the &lt;strong&gt;Create parameter&lt;/strong&gt; page.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Name&lt;/strong&gt;, enter one of the following:
&lt;ul&gt;
&lt;li&gt;Variable: &lt;code&gt;/{project}/{environment}/config/scheduler/public/{variable_name}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Secret: &lt;code&gt;/{project}/{environment}/secret/scheduler/public/{variable_name}&lt;/code&gt;
Make sure to replace the placeholders based on your requirements.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Optional: For &lt;strong&gt;Description&lt;/strong&gt;, enter a description of the variable. This may be a note about what this variable is used for.
This may be a note about what this variable is used for.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Type&lt;/strong&gt;, select &lt;strong&gt;SecureString&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Value&lt;/strong&gt;, enter the value of the variable.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Create parameter&lt;/strong&gt;.
This opens the &lt;strong&gt;Parameter Store&lt;/strong&gt; page with a success message displayed.&lt;/li&gt;
&lt;li&gt;Go to &lt;strong&gt;Services &lt;span aria-label=&quot;and then&quot;&gt;&amp;gt;&lt;/span&gt; CodePipeline&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;On the &lt;strong&gt;Pipelines&lt;/strong&gt; page, select the &lt;strong&gt;Rollout_Scheduler_{project}-{environment}&lt;/strong&gt; pipeline.&lt;/li&gt;
&lt;li&gt;On the pipeline’s page, click &lt;strong&gt;Release change&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In the &lt;strong&gt;Release change&lt;/strong&gt; window, click &lt;strong&gt;Release&lt;/strong&gt;.
After the pipeline finishes running, the variable gets available for your application.&lt;/li&gt;
&lt;/ol&gt;
</description>
            <pubDate>Fri, 03 Jul 2026 15:04:00 +0000</pubDate>
            <link>https://docs.spryker.com/docs/ca/dev/add-variables-in-the-parameter-store.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/ca/dev/add-variables-in-the-parameter-store.html</guid>
            
            
        </item>
        
        <item>
            <title>Recurring Orders feature overview</title>
            <description>{% info_block warningBox &quot;Early Access&quot; %}

This feature is in Early Access. We&apos;d love for you to try it out and share feedback as we work toward general availability.

{% endinfo_block %}

The *Recurring Orders* feature lets B2B buyers set up automated repeat purchases directly from the checkout. Once configured, the system places orders automatically at the chosen interval, sends notifications before each execution, and pauses for buyer review when prices change or products become unavailable.

![Recurring order list](https://spryker.s3.eu-central-1.amazonaws.com/docs/Features/Recurring+Orders/RecurringOrders_1.png)

![Recurring order detail](https://spryker.s3.eu-central-1.amazonaws.com/docs/Features/Recurring+Orders/RecurringOrders_2.png)

## Concepts

| TERM | DESCRIPTION |
| --- | --- |
| Recurring schedule | The configuration record that drives automated order placement. Stores the cadence, the serialized quote snapshot, and the state machine state. |
| Cadence | The interval at which the order is placed. One of: weekly, bi-weekly, monthly, or every N weeks. |
| Trigger date | The date on which the state machine attempts to place the next order. |
| Notification window | The number of hours before the trigger date when the pre-trigger notification is sent to the buyer. |
| Review Required | A state the schedule enters when price increases or product issues are detected at pre-placement validation. The buyer must accept or adjust the order before it is placed. |

## Setting up a recurring order

At checkout, an eligible buyer can enable the recurring order setup widget. The buyer selects a cadence (for example, weekly or monthly) and optionally sets a schedule name and an interval value for the *every N weeks* cadence.

When the order is placed, the system:

1. Saves a serialized snapshot of the quote — including products, quantities, prices, shipment method, and payment method.
2. Creates a recurring schedule record in `spy_recurring_schedule` with the first trigger date calculated from the cadence.
3. Registers the schedule with the `RecurringOrder` state machine in the `draft` state and immediately activates it.

A recurring schedule is **only available** for quotes that meet all of the following conditions:

- The quote is not locked (not sent for approval).
- The quote does not originate from a Request for Quote (RFQ).
- The customer is not a guest.
- The payment method is invoice-based (`invoice`, `purchaseOnAccount`, or a configured equivalent).

## Cadence types

| CADENCE | DESCRIPTION |
| --- | --- |
| Weekly | Places an order every 7 days. |
| Bi-weekly | Places an order every 14 days. |
| Monthly | Places an order on the same calendar day each month. If the scheduled day does not exist in the target month, the date overflows: for example, a schedule anchored to January 31 next fires on March 3 (not February 28), and all subsequent executions are anchored to the 3rd of each month. To avoid drift, use a start date on the 28th or earlier. |
| Every N weeks | Places an order every N weeks. Requires a positive integer value for N. |

![Recurring order setup at checkout](https://spryker.s3.eu-central-1.amazonaws.com/docs/Features/Recurring+Orders/RecurringOrders_3.png)

## Schedule lifecycle

The recurring schedule moves through states managed by the `RecurringOrder` state machine. The following diagram describes the full lifecycle:

| STATE | DESCRIPTION |
| --- | --- |
| `draft` | Newly created. Transitions to `active` immediately after checkout. |
| `active` | Running. The state machine checks the trigger date on every cron run. |
| `notifying` | The trigger date is within the notification window. The system sends a pre-trigger notification to the buyer. |
| `pre_trigger_notified` | The buyer has been notified. The schedule waits for the placement window to open or for a manual action (skip or cancel). |
| `validation` | Pre-placement validation is running. Price and availability are checked. |
| `confirmed` | Validation passed. The order is ready for placement. |
| `order_placed` | The checkout has been initiated. The system waits for confirmation. |
| `completing` | The order was successfully placed. The next trigger date is calculated and the schedule returns to `active`. |
| `skipped` | The buyer skipped the current execution. The next trigger date is advanced by one full cadence interval. |
| `review_required` | Validation detected an issue (price increase or product unavailability). The buyer must review before placement can proceed. |
| `paused` | The buyer manually paused the schedule. No orders are placed until it is resumed. |
| `failed` | The last order placement attempt failed. The buyer can retry, which moves the schedule to `review_required`. |
| `cancelled` | The schedule has been permanently stopped. This is a terminal state. |

### Buyer actions

Buyers can perform the following manual actions from the recurring order detail page on the storefront:

| ACTION | AVAILABLE FROM STATES | DESCRIPTION |
| --- | --- | --- |
| Pause | `active` | Temporarily stops order placement. The schedule can be resumed at any time with an optional custom resume date. |
| Resume | `paused` | Reactivates the schedule. The buyer can set a new next trigger date or keep the existing one. |
| Skip | `active`, `pre_trigger_notified`, `review_required` | Skips the next scheduled execution. The new trigger date is calculated by advancing the current trigger date by one cadence interval. If the current trigger date is already in the past due to processing lag, the recalculated date may also fall in the past and the schedule will process on the next cron run. |
| Cancel | `active`, `paused`, `pre_trigger_notified`, `review_required`, `failed`, `draft` | Permanently cancels the schedule. This action cannot be undone. The `draft` state is transient and is normally activated synchronously at checkout; cancellation from `draft` is a safety fallback. |
| Review | `review_required` | Opens the Review Required page where the buyer can accept price changes, remove unavailable items, and place the order. |
| Retry | `failed` | Moves the schedule to `review_required` so the buyer can review and re-attempt placement. |

![Recurring order list with attention banner](https://spryker.s3.eu-central-1.amazonaws.com/docs/Features/Recurring+Orders/RecurringOrders_4.png)

## Pre-trigger notification

Before each order placement, the system sends an email to the buyer within the configured **Schedule Grace Period** (default: 48 hours before the trigger date). The notification includes:

- The schedule name and the upcoming execution date.
- A link to the schedule detail page where the buyer can skip, pause, or cancel before the order is placed.

The Schedule Grace Period is configured globally in the Back Office under **Configuration &gt; Recurring Orders &gt; General &gt; Schedule**.

## Review Required flow

Before placing each order, the system validates the stored quote snapshot against current product and pricing data. If issues are detected, the schedule moves to the `review_required` state and the buyer receives a review notification email.

The buyer reviews the flagged items on the **Review Required** page. The following table lists common issue types. The full set of checkout error types that map to each group is configurable via `getReviewReasonGroupMap()` in `OrderExperienceManagementConfig`.

| ISSUE | DESCRIPTION |
| --- | --- |
| Price increased | The current unit price is higher than the reference price stored on the schedule item. |
| Unavailable | The product is out of stock, inactive, or blocked by a merchant or product approval rule. |
| Packaging unit unavailable | The product packaging unit constraints cannot be satisfied — for example, the required minimum or lead quantity is not available. |
| Discontinued | The product has been discontinued. |
| Substituted | The product has been replaced by another product. |
| Not approved | The product is pending approval and cannot be purchased. |
| Price unavailable | No current price could be resolved for the product. |
| Configurable bundle unavailable | A member of a configurable bundle is unpurchasable, so the entire bundle is dropped. |

Items flagged as **unavailable** or **not approved** are non-purchasable and must be removed before the order can proceed. Items with a price increase can be accepted or removed. The buyer confirms the changes, which updates the stored quote snapshot and places the order.

![Review Required page](https://spryker.s3.eu-central-1.amazonaws.com/docs/Features/Recurring+Orders/RecurringOrders_5.png)

## Execution history

Each recurring schedule maintains a full execution history. Every significant event is recorded:

| EVENT | DESCRIPTION |
| --- | --- |
| Placed | An order was successfully placed. The history entry links to the resulting sales order. |
| Failed | An order placement attempt failed. The entry includes the failure reason. |
| Skipped | The execution was skipped by the buyer. |
| Paused | The schedule was paused. |
| Resumed | The schedule was resumed. |
| Cancelled | The schedule was permanently cancelled. |

## Storefront pages

| PAGE | PATH | DESCRIPTION |
| --- | --- | --- |
| Recurring order list | `/recurring-orders` | Lists all recurring schedules for the current buyer, with status, cadence, and trigger date. Company users with the appropriate permission can filter by scope (own, company, or business unit). |
| Recurring order detail | `/recurring-orders/{uuid}` | Shows the full schedule configuration, the items and quantities, the next execution date, and the full execution history. |
| Review Required | `/recurring-orders/{uuid}/review-required` | Shows flagged items with issue reasons and price comparisons. The buyer accepts changes and places the order from this page. |

## B2B visibility and permissions

By default, a buyer can only view their own recurring schedules. Company users with additional permissions can view schedules across their organization:

| PERMISSION | DESCRIPTION |
| --- | --- |
| `SeeCompanyOrdersPermissionPlugin` | Grants visibility over all recurring schedules within the company. |
| `SeeBusinessUnitOrdersPermissionPlugin` | Grants visibility over all recurring schedules within the buyer&apos;s business unit. |

These permissions are registered as company role permissions and assigned in the Back Office under **Customers &gt; Company Roles**.

## Attention banner

When a buyer has schedules in the `paused`, `review_required`, or `failed` states, an attention banner is displayed on the storefront. The banner shows the count of schedules requiring attention and provides quick-access links to filter the recurring order list by each status.

## Related documents

- [Install the Recurring Orders feature](/docs/pbc/all/order-experience-management/latest/base-shop/install-and-upgrade/install-features/install-the-recurring-orders-feature.html)
</description>
            <pubDate>Tue, 30 Jun 2026 14:54:21 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/order-experience-management/latest/base-shop/feature-overviews/recurring-orders-feature-overview.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/order-experience-management/latest/base-shop/feature-overviews/recurring-orders-feature-overview.html</guid>
            
            
        </item>
        
        <item>
            <title>Install the Recurring Orders feature</title>
            <description>{% info_block warningBox &quot;Early Access&quot; %}

This feature is in Early Access. We&apos;d love for you to try it out and share feedback as we work toward general availability.

{% endinfo_block %}

This document describes how to install the Recurring Orders feature.

## Install feature core

Follow the steps below to install the Recurring Orders feature core.

### Prerequisites

To start feature integration, review and install the necessary features:

| NAME | VERSION | INSTALLATION GUIDE |
| --- | --- | --- |
| Spryker Core | {{page.release_tag}} | [Install the Spryker Core feature](/docs/pbc/all/miscellaneous/latest/install-and-upgrade/install-features/install-the-spryker-core-feature.html) |
| Company Account | {{page.release_tag}} | [Install the Company Account feature](/docs/pbc/all/customer-relationship-management/latest/base-shop/install-and-upgrade/install-features/install-the-company-account-feature.html) |
| Checkout | {{page.release_tag}} | [Install the Checkout feature](/docs/pbc/all/cart-and-checkout/latest/base-shop/install-and-upgrade/install-features/install-the-checkout-feature.html) |

### 1) Install the required modules

{% info_block infoBox &quot;Required modules&quot; %}

```bash
composer require spryker-feature/order-experience-management:&quot;^0.1.4&quot; --update-with-dependencies
composer update \
  spryker/availability:&quot;^9.32.0&quot; \
  spryker/merchant:&quot;^3.20.0&quot; \
  spryker/merchant-product-option:&quot;^1.4.0&quot; \
  spryker/merchant-switcher:&quot;^0.6.8&quot; \
  spryker/product-approval:&quot;^1.5.0&quot; \
  spryker/product-bundle:&quot;^7.28.0&quot; \
  spryker/product-cart-connector:&quot;^4.15.0&quot; \
  spryker/product-configuration-cart:&quot;^1.1.0&quot; \
  spryker/product-discontinued:&quot;^1.15.0&quot; \
  spryker/product-offer:&quot;^1.18.0&quot; \
  spryker/product-packaging-unit:&quot;^4.14.0&quot; \
  spryker/product-quantity:&quot;^3.8.0&quot; \
  spryker-feature/purchasing-control:&quot;^1.1.1&quot; \
  spryker-shop/checkout-page:&quot;^3.42.0&quot; \
  spryker-shop/customer-page:&quot;^2.80.0&quot; \
  --with-dependencies
```

{% endinfo_block %}

### 2) Set up database schema and transfer objects

Apply database changes and generate entity and transfer changes:

```bash
console propel:install
console transfer:generate
```

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

Make sure the following changes have been applied in the database:

| DATABASE ENTITY | TYPE | EVENT |
| --- | --- | --- |
| spy_recurring_schedule | table | created |
| spy_recurring_schedule_item | table | created |
| spy_recurring_schedule_history | table | created |

Make sure the following changes have been applied in transfer objects:

| TRANSFER | TYPE | EVENT | PATH |
| --- | --- | --- | --- |
| RecurringSchedule | class | created | src/Generated/Shared/Transfer/RecurringScheduleTransfer.php |
| RecurringScheduleCollection | class | created | src/Generated/Shared/Transfer/RecurringScheduleCollectionTransfer.php |
| RecurringScheduleCriteria | class | created | src/Generated/Shared/Transfer/RecurringScheduleCriteriaTransfer.php |
| RecurringScheduleConditions | class | created | src/Generated/Shared/Transfer/RecurringScheduleConditionsTransfer.php |
| RecurringScheduleCollectionRequest | class | created | src/Generated/Shared/Transfer/RecurringScheduleCollectionRequestTransfer.php |
| RecurringScheduleCollectionResponse | class | created | src/Generated/Shared/Transfer/RecurringScheduleCollectionResponseTransfer.php |
| RecurringScheduleItem | class | created | src/Generated/Shared/Transfer/RecurringScheduleItemTransfer.php |
| RecurringScheduleHistory | class | created | src/Generated/Shared/Transfer/RecurringScheduleHistoryTransfer.php |
| RecurringScheduleValidationResult | class | created | src/Generated/Shared/Transfer/RecurringScheduleValidationResultTransfer.php |
| RecurringScheduleItemReview | class | created | src/Generated/Shared/Transfer/RecurringScheduleItemReviewTransfer.php |
| RecurringScheduleReviewResponse | class | created | src/Generated/Shared/Transfer/RecurringScheduleReviewResponseTransfer.php |
| RecurringScheduleEventRequest | class | created | src/Generated/Shared/Transfer/RecurringScheduleEventRequestTransfer.php |
| RecurringScheduleEventResponse | class | created | src/Generated/Shared/Transfer/RecurringScheduleEventResponseTransfer.php |
| RecurringScheduleStatusCountCollection | class | created | src/Generated/Shared/Transfer/RecurringScheduleStatusCountCollectionTransfer.php |
| RecurringOrderSettings | class | created | src/Generated/Shared/Transfer/RecurringOrderSettingsTransfer.php |
| RecurringOrderQuoteUpdateRequest | class | created | src/Generated/Shared/Transfer/RecurringOrderQuoteUpdateRequestTransfer.php |
| RecurringOrderQuoteUpdateResponse | class | created | src/Generated/Shared/Transfer/RecurringOrderQuoteUpdateResponseTransfer.php |
| Quote.recurringOrderSettings | property | created | src/Generated/Shared/Transfer/QuoteTransfer.php |

{% endinfo_block %}

### 3) Set up data import

Import the CMS blocks that provide the HTML and text templates for recurring order notification emails.

The CMS block definitions are provided in the module at `src/SprykerFeature/OrderExperienceManagement/data/import/cms_block.csv`. Copy the contents of that file and add them to **data/import/common/common/cms_block.csv**.

For each store you want to enable the email notifications in, add the corresponding block keys to **data/import/common/{store}/cms_block_store.csv**.

Import the data:

```bash
console data:import:cms-block
console data:import:cms-block-store
```

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

In the Back Office, under **Content &gt; Blocks**, make sure the CMS blocks from the module file are present and active.

{% endinfo_block %}

### 4) Set up behavior

Enable the following behaviors by registering the plugins.

#### Set up Checkout plugins

| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
| --- | --- | --- | --- |
| RecurringOrderCheckoutPreConditionPlugin | Validates the quote is eligible for a recurring order before checkout proceeds. Checks that the quote is not locked, not from an RFQ, not a guest session, the payment method is invoice-based, and the cadence type is registered and valid. | None | SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Checkout |
| RecurringOrdersCheckoutPostSavePlugin | Creates a recurring schedule and registers it with the state machine after the order is successfully saved. Does nothing when `recurringOrderSettings` is not set on the quote. | None | SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Checkout |

**src/Pyz/Zed/Checkout/CheckoutDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Zed\Checkout;

use Spryker\Zed\Checkout\CheckoutDependencyProvider as SprykerCheckoutDependencyProvider;
use Spryker\Zed\Kernel\Container;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Checkout\RecurringOrderCheckoutPreConditionPlugin;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Checkout\RecurringOrdersCheckoutPostSavePlugin;

class CheckoutDependencyProvider extends SprykerCheckoutDependencyProvider
{
    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return list&lt;\Spryker\Zed\CheckoutExtension\Dependency\Plugin\CheckoutPreConditionPluginInterface&gt;
     */
    protected function getCheckoutPreConditions(Container $container): array
    {
        return [
            // ...
            new RecurringOrderCheckoutPreConditionPlugin(), #RecurringOrdersFeature
        ];
    }

    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return list&lt;\Spryker\Zed\CheckoutExtension\Dependency\Plugin\CheckoutPostSaveInterface&gt;
     */
    protected function getCheckoutPostHooks(Container $container): array
    {
        return [
            // ...
            new RecurringOrdersCheckoutPostSavePlugin(), #RecurringOrdersFeature
        ];
    }
}
```

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

1. Add a product to the cart, set a recurring order cadence on the quote, and complete checkout. Make sure a recurring schedule is created in `spy_recurring_schedule`.
2. Attempt to place a recurring order with a non-invoice payment method. Make sure checkout is blocked.
3. Attempt to place a recurring order with an invalid cadence type. Make sure checkout is blocked.

{% endinfo_block %}

#### Set up the Subscription dependency provider

Register the built-in cadence type and schedule validator plugins:

| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
| --- | --- | --- | --- |
| WeeklyCadenceTypePlugin | Calculates the next trigger date 7 days after the current trigger date. | None | SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Cadence |
| BiWeeklyCadenceTypePlugin | Calculates the next trigger date 14 days after the current trigger date. | None | SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Cadence |
| MonthlyCadenceTypePlugin | Calculates the next trigger date on the same day of the following month. | None | SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Cadence |
| EveryNWeeksCadenceTypePlugin | Calculates the next trigger date every N weeks. Requires `cadenceValue` to be set on the schedule. | None | SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Cadence |
| PriceScheduleValidatorPlugin | Detects price increases on recurring schedule items compared to their stored reference prices before order placement. | None | SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\ScheduleValidator |
| CheckoutPlaceabilityScheduleValidatorPlugin | Simulates a checkout to detect availability or product approval issues on recurring schedule items before order placement. | None | SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\ScheduleValidator |

**src/Pyz/Zed/OrderExperienceManagement/OrderExperienceManagementDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Zed\OrderExperienceManagement;

use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Cadence\BiWeeklyCadenceTypePlugin;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Cadence\EveryNWeeksCadenceTypePlugin;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Cadence\MonthlyCadenceTypePlugin;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Cadence\WeeklyCadenceTypePlugin;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\ScheduleValidator\CheckoutPlaceabilityScheduleValidatorPlugin;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\ScheduleValidator\PriceScheduleValidatorPlugin;
use SprykerFeature\Zed\OrderExperienceManagement\OrderExperienceManagementDependencyProvider as SprykerOrderExperienceManagementDependencyProvider;

class OrderExperienceManagementDependencyProvider extends SprykerOrderExperienceManagementDependencyProvider
{
    /**
     * @return array&lt;\SprykerFeature\Zed\OrderExperienceManagement\Dependency\Plugin\CadenceTypePluginInterface&gt;
     */
    protected function getCadenceTypePlugins(): array
    {
        return [
            new WeeklyCadenceTypePlugin(), #RecurringOrdersFeature
            new BiWeeklyCadenceTypePlugin(), #RecurringOrdersFeature
            new MonthlyCadenceTypePlugin(), #RecurringOrdersFeature
            new EveryNWeeksCadenceTypePlugin(), #RecurringOrdersFeature
        ];
    }

    /**
     * @return array&lt;\SprykerFeature\Zed\OrderExperienceManagement\Dependency\Plugin\ScheduleValidatorPluginInterface&gt;
     */
    protected function getScheduleValidatorPlugins(): array
    {
        return [
            new PriceScheduleValidatorPlugin(), #RecurringOrdersFeature
            new CheckoutPlaceabilityScheduleValidatorPlugin(), #RecurringOrdersFeature
        ];
    }
}
```

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

Make sure all four cadence types (weekly, bi-weekly, monthly, every N weeks) are available when setting up a recurring order on the storefront.

{% endinfo_block %}

#### Set up the state machine handler

Register the recurring orders state machine handler:

| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
| --- | --- | --- | --- |
| RecurringOrdersStateMachineHandlerPlugin | Registers the `RecurringOrder` state machine process, maps commands and conditions to plugins, updates the state machine item state on each transition, and returns schedule items by state IDs. | None | SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\StateMachine |

**src/Pyz/Zed/StateMachine/StateMachineDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Zed\StateMachine;

use Spryker\Zed\StateMachine\StateMachineDependencyProvider as SprykerStateMachineDependencyProvider;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\StateMachine\RecurringOrdersStateMachineHandlerPlugin;

class StateMachineDependencyProvider extends SprykerStateMachineDependencyProvider
{
    /**
     * @return array&lt;\Spryker\Zed\StateMachine\Dependency\Plugin\StateMachineHandlerInterface&gt;
     */
    protected function getStateMachineHandlers(): array
    {
        return [
            // ...
            new RecurringOrdersStateMachineHandlerPlugin(), #RecurringOrdersFeature
        ];
    }
}
```

Copy the state machine process XML from the module into your project. The example file is located at `src/SprykerFeature/OrderExperienceManagement/config/Zed/StateMachine/RecurringOrder/RecurringOrderStateMachine.xml` in the module. Add it to your project at the following path:

**config/Zed/StateMachine/RecurringOrder/RecurringOrderStateMachine.xml**

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

In the Back Office, under **Maintenance &gt; State Machine**, make sure the `RecurringOrderStateMachine` process is listed and the diagram renders correctly.

{% endinfo_block %}

#### Set up Mail plugins

Register the following mail type builder plugins:

| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
| --- | --- | --- | --- |
| RecurringOrderUpcomingNotificationMailTypeBuilderPlugin | Builds the pre-trigger notification email sent to the buyer a configurable number of hours before the scheduled order is placed. | None | SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Mail |
| RecurringOrderValidationFailedMailTypeBuilderPlugin | Builds the review-required notification email sent to the buyer when a price increase or product availability issue is detected. | None | SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Mail |
| RecurringOrderFailureMailTypeBuilderPlugin | Builds the order placement failure notification email sent to the buyer when order placement fails. | None | SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Mail |

**src/Pyz/Zed/Mail/MailDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Zed\Mail;

use Spryker\Zed\Mail\MailDependencyProvider as SprykerMailDependencyProvider;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Mail\RecurringOrderFailureMailTypeBuilderPlugin;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Mail\RecurringOrderUpcomingNotificationMailTypeBuilderPlugin;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Plugin\Mail\RecurringOrderValidationFailedMailTypeBuilderPlugin;

class MailDependencyProvider extends SprykerMailDependencyProvider
{
    /**
     * @return array&lt;\Spryker\Zed\MailExtension\Dependency\Plugin\MailTypeBuilderPluginInterface&gt;
     */
    protected function getMailTypeBuilderPlugins(): array
    {
        return [
            // ...
            new RecurringOrderUpcomingNotificationMailTypeBuilderPlugin(), #RecurringOrdersFeature
            new RecurringOrderValidationFailedMailTypeBuilderPlugin(), #RecurringOrdersFeature
            new RecurringOrderFailureMailTypeBuilderPlugin(), #RecurringOrdersFeature
        ];
    }
}
```

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

Trigger a recurring order cycle and verify the following:
- A pre-trigger notification email is sent to the buyer within the configured notification window hours.
- When a price increase or product availability issue is detected, a review-required email is sent.
- When order placement fails, a failure notification email is sent.

{% endinfo_block %}

#### Set up permissions

By default, a company user can only see their own recurring orders. To allow users to view recurring orders across their company or business unit, register the following permission plugins:

| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
| --- | --- | --- | --- |
| SeeCompanyOrdersPermissionPlugin | Grants permission to view all recurring orders within the company. Assign to company roles that should have company-wide visibility. | None | Spryker\Zed\CompanySalesConnector\Communication\Plugin\Permission |
| SeeBusinessUnitOrdersPermissionPlugin | Grants permission to view all recurring orders within the company business unit. Assign to company roles that should have business-unit-wide visibility. | None | Spryker\Zed\CompanyBusinessUnitSalesConnector\Communication\Plugin\Permission |

**src/Pyz/Zed/Permission/PermissionDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Zed\Permission;

use Spryker\Zed\CompanyBusinessUnitSalesConnector\Communication\Plugin\Permission\SeeBusinessUnitOrdersPermissionPlugin;
use Spryker\Zed\CompanySalesConnector\Communication\Plugin\Permission\SeeCompanyOrdersPermissionPlugin;
use Spryker\Zed\Permission\PermissionDependencyProvider as SprykerPermissionDependencyProvider;

class PermissionDependencyProvider extends SprykerPermissionDependencyProvider
{
    /**
     * @return array&lt;\Spryker\Shared\PermissionExtension\Dependency\Plugin\PermissionPluginInterface&gt;
     */
    protected function getPermissionPlugins(): array
    {
        return [
            // ...
            new SeeCompanyOrdersPermissionPlugin(), #RecurringOrdersFeature
            new SeeBusinessUnitOrdersPermissionPlugin(), #RecurringOrdersFeature
        ];
    }
}
```

**src/Pyz/Client/Permission/PermissionDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Client\Permission;

use Spryker\Client\CompanyBusinessUnitSalesConnector\Plugin\Permission\SeeBusinessUnitOrdersPermissionPlugin;
use Spryker\Client\CompanySalesConnector\Plugin\Permission\SeeCompanyOrdersPermissionPlugin;
use Spryker\Client\Permission\PermissionDependencyProvider as SprykerPermissionDependencyProvider;

class PermissionDependencyProvider extends SprykerPermissionDependencyProvider
{
    /**
     * @return array&lt;\Spryker\Shared\PermissionExtension\Dependency\Plugin\PermissionPluginInterface&gt;
     */
    protected function getPermissionPlugins(): array
    {
        return [
            // ...
            new SeeCompanyOrdersPermissionPlugin(), #RecurringOrdersFeature
            new SeeBusinessUnitOrdersPermissionPlugin(), #RecurringOrdersFeature
        ];
    }
}
```

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

In the Back Office, under **Customers &gt; Company Roles**, assign `SeeCompanyOrdersPermissionPlugin` to a company role. Make sure company users with that role can see all recurring orders within their company on the storefront.

Assign `SeeBusinessUnitOrdersPermissionPlugin` to a role. Make sure users with that role can see all recurring orders within their business unit.

{% endinfo_block %}

#### Set up cron jobs

The recurring orders state machine relies on a cron job to evaluate condition transitions. Register the job in your Jenkins configuration:

**config/Zed/cronjobs/jenkins.php**

```php
/* RecurringOrder StateMachine */
$jobs[] = [
    &apos;name&apos; =&gt; &apos;recurring-order-check-conditions&apos;,
    &apos;command&apos; =&gt; &apos;$PHP_BIN vendor/bin/console state-machine:check-condition RecurringOrder&apos;,
    &apos;schedule&apos; =&gt; &apos;* * * * *&apos;,
    &apos;enable&apos; =&gt; true,
];
```

{% info_block infoBox &quot;Scheduling recommendation&quot; %}

To have recurring orders placed before the business day starts, set trigger dates to an early morning time (for example, 01:00) and configure `DEFAULT_NOTIFICATION_WINDOW_HOURS` to `18` (for example) or more. With an 18-hour window, the pre-trigger notification is sent the previous afternoon (after 12:00), allowing the buyer to review or skip before the order is placed overnight.

{% endinfo_block %}

If your project uses Symfony Scheduler instead of Jenkins, register the equivalent job in your scheduler config:

**src/Pyz/Zed/SymfonyScheduler/SymfonySchedulerConfig.php**

```php
&apos;recurring-orders-check-condition&apos; =&gt; [
    &apos;command&apos; =&gt; &apos;$PHP_BIN vendor/bin/console state-machine:check-condition RecurringOrder&apos;,
    &apos;schedule&apos; =&gt; &apos;* * * * *&apos;,
],
&apos;recurring-orders-clear-locks&apos; =&gt; [
    &apos;command&apos; =&gt; &apos;$PHP_BIN vendor/bin/console state-machine:clear-locks&apos;,
    &apos;schedule&apos; =&gt; &apos;0 6 * * *&apos;,
],
```

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

Activate a recurring schedule. Make sure the state machine condition check job runs and the schedule transitions from `draft` to `active` within one minute.

{% endinfo_block %}

#### Optional: Register the trigger console command

`RecurringOrderTriggerConsole` lets you manually trigger order placement for a specific recurring schedule from the CLI. It runs the same placement logic as the state machine `PlaceOrderCommand`, which makes it useful for development, debugging, and one-off operational tasks.

To enable it, register the command in your console dependency provider:

**src/Pyz/Zed/Console/ConsoleDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Zed\Console;

use Spryker\Zed\Console\ConsoleDependencyProvider as SprykerConsoleDependencyProvider;
use SprykerFeature\Zed\OrderExperienceManagement\Communication\Console\RecurringOrderTriggerConsole;

class ConsoleDependencyProvider extends SprykerConsoleDependencyProvider
{
    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return list&lt;\Symfony\Component\Console\Command\Command&gt;
     */
    protected function getConsoleCommands(Container $container): array
    {
        return [
            // ...
            new RecurringOrderTriggerConsole(), #RecurringOrdersFeature
        ];
    }
}
```

Usage:

```bash
# Trigger placement by numeric ID
console recurring-orders:trigger-placement 42

# Trigger placement by UUID
console recurring-orders:trigger-placement 550e8400-e29b-41d4-a716-446655440000

# Run pre-placement validation first; aborts if validation fails
console recurring-orders:trigger-placement 42 --validate
```

The `--validate` flag runs all registered `ScheduleValidatorPlugin` implementations (for example, `PriceScheduleValidatorPlugin` and `CheckoutPlaceabilityScheduleValidatorPlugin`) before attempting placement. If any validator reports a failure, the command exits with an error and does not place the order.

{% info_block warningBox &quot;Development use only&quot; %}

This command is intended for development and debugging. Do not use it in production automated pipelines — the state machine cron job is the intended trigger for production order placement.

{% endinfo_block %}

#### Configure product bundle field copying

{% info_block infoBox &quot;Product Bundles feature&quot; %}

This step is only required if your project uses the [Product Bundles feature](/docs/pbc/all/product-information-management/latest/base-shop/feature-overviews/product-bundles-feature-overview.html).

{% endinfo_block %}

Override the allowed fields to copy so that shipment information is preserved when recurring orders re-create bundle items:

| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
| --- | --- | --- | --- |
| `getAllowedBundleItemFieldsToCopy()` | Returns the list of `ItemTransfer` fields copied from bundle items when they are duplicated during order re-creation. Adding `ItemTransfer::SHIPMENT` ensures the shipment is preserved on each recurring order placement. | None | Pyz\Zed\ProductBundle |

**src/Pyz/Zed/ProductBundle/ProductBundleConfig.php**

```php
&lt;?php

namespace Pyz\Zed\ProductBundle;

use Generated\Shared\Transfer\ItemTransfer;
use Spryker\Zed\ProductBundle\ProductBundleConfig as SprykerProductBundleConfig;

class ProductBundleConfig extends SprykerProductBundleConfig
{
    /**
     * @return list&lt;string&gt;
     */
    public function getAllowedBundleItemFieldsToCopy(): array
    {
        return [
            ItemTransfer::SHIPMENT,
        ];
    }
}
```

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

Add a product bundle to the cart, set up a recurring order, and complete checkout. On the next scheduled order placement, make sure the bundle items are re-created with the correct shipment assignment.

{% endinfo_block %}

### 5) Configure module behavior

Override the following configuration methods in your project (if needed) to adjust the default behavior:

**src/Pyz/Zed/OrderExperienceManagement/OrderExperienceManagementConfig.php**

```php
&lt;?php

namespace Pyz\Zed\OrderExperienceManagement;

use SprykerFeature\Shared\OrderExperienceManagement\OrderExperienceManagementConfig as SharedOrderExperienceManagementConfig;
use SprykerFeature\Zed\OrderExperienceManagement\OrderExperienceManagementConfig as SprykerOrderExperienceManagementConfig;

class OrderExperienceManagementConfig extends SprykerOrderExperienceManagementConfig
{
    /**
     * Specification:
     * - Returns the number of hours before the trigger date when the pre-trigger notification is sent.
     * - Default: 48 hours.
     * - Overriding this value affects all schedules that do not have a per-schedule override.
     *
     * @api
     */
    public function getDefaultNotificationWindowHours(): int
    {
        return 18;
    }

    /**
     * Specification:
     * - Returns a map of review reason groups to the checkout error types that resolve to them.
     * - Override to add custom checkout error types to existing groups or to introduce new groups.
     * - The key is a SharedOrderExperienceManagementConfig::REVIEW_REASON_GROUP_* constant.
     * - The value is a list of raw checkout error type strings reported by the checkout facade.
     *
     * @api
     *
     * @return array&lt;string, array&lt;string&gt;&gt;
     */
    public function getReviewReasonGroupMap(): array
    {
        return array_merge_recursive(parent::getReviewReasonGroupMap(), [
            SharedOrderExperienceManagementConfig::REVIEW_REASON_GROUP_UNAVAILABLE =&gt; [
                // Add project-specific checkout error types here.
            ],
        ]);
    }

    /**
     * Specification:
     * - Returns the review reason groups whose items are treated as non-purchasable.
     * - Items in these groups block order placement and must be removed before the order can proceed.
     * - Default: [REVIEW_REASON_GROUP_UNAVAILABLE].
     * - Override to add REVIEW_REASON_GROUP_DISCONTINUED if discontinued items should also block placement.
     *
     * @api
     *
     * @return array&lt;string&gt;
     */
    public function getNonPurchasableReviewReasonGroups(): array
    {
        return [
            SharedOrderExperienceManagementConfig::REVIEW_REASON_GROUP_UNAVAILABLE,
            SharedOrderExperienceManagementConfig::REVIEW_REASON_GROUP_DISCONTINUED,
        ];
    }
}
```

| CONFIGURATION METHOD | DEFAULT | DESCRIPTION |
| --- | --- | --- |
| `getDefaultNotificationWindowHours()` | `48` | Number of hours before the trigger date when the pre-trigger notification is sent. |
| `getReviewReasonGroupMap()` | See `OrderExperienceManagementConfig` | Maps review reason groups to checkout error types. Extend to map project-specific error types to the appropriate review group. |
| `getNonPurchasableReviewReasonGroups()` | `[REVIEW_REASON_GROUP_UNAVAILABLE]` | Review reason groups whose items block order placement and must be removed before the order can proceed. Override to also block on `REVIEW_REASON_GROUP_DISCONTINUED`. |

## Install feature frontend

Follow the steps below to install the Recurring Orders feature frontend.

### 1) Set up routes

Register the following route provider plugin:

| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
| --- | --- | --- | --- |
| RecurringOrderRouteProviderPlugin | Adds storefront routes for the recurring order list, detail, create, clear, pause, resume, skip, cancel, confirm, review, and approve-review actions. | None | SprykerFeature\Yves\OrderExperienceManagement\Plugin\Router |

**src/Pyz/Yves/Router/RouterDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Yves\Router;

use Spryker\Yves\Router\RouterDependencyProvider as SprykerRouterDependencyProvider;
use SprykerFeature\Yves\OrderExperienceManagement\Plugin\Router\RecurringOrderRouteProviderPlugin;

class RouterDependencyProvider extends SprykerRouterDependencyProvider
{
    /**
     * @return array&lt;\Spryker\Yves\RouterExtension\Dependency\Plugin\RouteProviderPluginInterface&gt;
     */
    protected function getRouteProvider(): array
    {
        return [
            // ...
            new RecurringOrderRouteProviderPlugin(), #RecurringOrdersFeature
        ];
    }
}
```

After registering the plugin, warm up the router caches:

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

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

Make sure the following storefront routes are accessible:
- `/recurring-orders` — recurring order list page.
- `/recurring-orders/{uuid}` — recurring order detail page.
- `/recurring-orders/{uuid}/review-required` — review required page.
- POST `/recurring-order/save` — saves recurring order settings on the quote.
- POST `/recurring-order/clear` — removes recurring order settings from the quote.

{% endinfo_block %}

### 2) Set up widgets

Register the following global widgets:

| WIDGET | DESCRIPTION | NAMESPACE |
| --- | --- | --- |
| RecurringOrderSelectorWidget | Renders the recurring order setup form at checkout. Visible only when the quote is eligible for a recurring order (invoice payment, not locked, not from RFQ, not guest). | SprykerFeature\Yves\OrderExperienceManagement\Widget |
| RecurringOrderMenuItemWidget | Renders the Recurring Orders navigation menu item in the storefront company menu. | SprykerFeature\Yves\OrderExperienceManagement\Widget |
| CostCenterDetailWidget | Displays the selected cost center and budget on the cart page. Takes a `QuoteTransfer` as input. Requires the [Purchasing Control feature](/docs/pbc/all/cart-and-checkout/latest/base-shop/install-and-upgrade/install-features/install-the-purchasing-control-feature.html). | SprykerFeature\Yves\PurchasingControl\Widget |

**src/Pyz/Yves/ShopApplication/ShopApplicationDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Yves\ShopApplication;

use SprykerFeature\Yves\PurchasingControl\Widget\CostCenterDetailWidget;
use SprykerFeature\Yves\OrderExperienceManagement\Widget\RecurringOrderMenuItemWidget;
use SprykerFeature\Yves\OrderExperienceManagement\Widget\RecurringOrderSelectorWidget;
use SprykerShop\Yves\ShopApplication\ShopApplicationDependencyProvider as SprykerShopApplicationDependencyProvider;

class ShopApplicationDependencyProvider extends SprykerShopApplicationDependencyProvider
{
    /**
     * @return array&lt;string&gt;
     */
    protected function getGlobalWidgets(): array
    {
        return [
            // ...
            RecurringOrderSelectorWidget::class, #RecurringOrdersFeature
            RecurringOrderMenuItemWidget::class, #RecurringOrdersFeature
            CostCenterDetailWidget::class, #RecurringOrdersFeature
        ];
    }
}
```

### 3) Add the recurring order selector to the checkout summary page

The `RecurringOrderSelectorWidget` is not rendered automatically — it must be explicitly called from the checkout summary template. Add it after any cost center or voucher sections, before the order form.

**src/Pyz/Yves/CheckoutPage/Theme/default/views/summary/summary.twig**

```twig
{% raw %}{% widget &apos;RecurringOrderSelectorWidget&apos; args [data.cart] only %}{% endwidget %}{% endraw %}
```

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

On the checkout summary page with an invoice-based payment method, make sure the **Set up as recurring order** checkbox and description are displayed.

{% endinfo_block %}

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

- On the cart page, make sure the selected cost center and budget names are displayed.

{% endinfo_block %}

### 4) Add the menu item to the customer navigation sidebar

To make the **Recurring Orders** menu item appear in the customer account sidebar, add a plain data entry to the `data.items` array in your project&apos;s customer navigation sidebar template. Place it after the Order History item.

**src/Pyz/Yves/CustomerPage/Theme/default/components/molecules/navigation-sidebar/navigation-sidebar.twig**

```twig
{% raw %}
{% define data = {
    items: [
        {# ... existing items ... #}
        {
            name: &apos;order&apos;,
            url: path(&apos;customer/order&apos;),
            label: &apos;customer.account.order_history&apos; | trans,
            icon: &apos;history&apos;,
        },
        {
            name: &apos;recurring-orders&apos;,
            url: path(&apos;recurring-orders&apos;),
            label: &apos;recurring_orders.menu_item&apos; | trans,
            icon: &apos;calendar&apos;,
        },
        {# ... remaining items ... #}
    ]
} %}
{% endraw %}
```

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

In the storefront customer account, make sure the **Recurring Orders** menu item appears directly below **Order History** in the left sidebar navigation and links to `/recurring-orders`.

{% endinfo_block %}

{% info_block infoBox &quot;Alternative: widget-based menu item&quot; %}

If your project&apos;s navigation sidebar template doesn&apos;t use a plain `data.items` array — for example, it&apos;s built from a custom navigation plugin or uses a different template structure — you can render the menu item via `RecurringOrderMenuItemWidget` instead. Make sure the widget is registered in `ShopApplicationDependencyProvider` (see [Set up widgets](#2-set-up-widgets)), then call it from the `postContent` block of your sidebar template:

```twig
{% raw %}
{% block postContent %}
    {# ... existing widget calls ... #}
    {% widget &apos;RecurringOrderMenuItemWidget&apos; args [data.activePage] only %}{% endwidget %}
{% endblock %}
{% endraw %}
```

The widget renders an `&lt;li&gt;` element and must be placed inside a `&lt;ul&gt;` context.

{% endinfo_block %}

### 5) Build the frontend

After making changes to Twig templates and registering new widgets, rebuild the Yves frontend assets:

```bash
npm run yves
```

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

Reload the checkout summary page and make sure the recurring order selector renders without console errors.

{% endinfo_block %}

### 7) Import glossary data

The full list of glossary keys is provided in the module at `src/SprykerFeature/OrderExperienceManagement/data/import/glossary.csv`. Copy the contents of that file and add them to **data/import/common/common/glossary.csv**.

Import data:

```bash
console data:import:glossary
```

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

Make sure that, in the database, the configured data has been added to the `spy_glossary_key` and `spy_glossary_translation` tables.

{% endinfo_block %}

### 8) Configure the Back Office settings

Sync the recurring orders configuration settings to the database to make them editable in the Back Office:

```bash
console configuration:sync
```

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

In the Back Office, go to **Configuration &gt; Recurring Orders &gt; General &gt; Schedule**. Make sure the **Schedule Grace Period** field is displayed with a default value of `48`.

{% endinfo_block %}

{% info_block infoBox &quot;Configurable settings&quot; %}

| SETTING | DEFAULT | DESCRIPTION |
| --- | --- | --- |
| Schedule Grace Period | `48` | Number of hours before the trigger date when the pre-trigger notification email is sent to the buyer. Per-schedule overrides stored in `spy_recurring_schedule.notification_window_hours` take precedence over this global value. |

{% endinfo_block %}
</description>
            <pubDate>Tue, 30 Jun 2026 14:54:21 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/order-experience-management/latest/base-shop/install-and-upgrade/install-features/install-the-recurring-orders-feature.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/order-experience-management/latest/base-shop/install-and-upgrade/install-features/install-the-recurring-orders-feature.html</guid>
            
            
        </item>
        
        <item>
            <title>Install the Purchasing Control feature</title>
            <description>{% info_block warningBox &quot;Experimental feature&quot; %}

Experimental feature - not recommended for production use.

{% endinfo_block %}

This document describes how to install the [Purchasing Control feature](/docs/pbc/all/cart-and-checkout/latest/base-shop/feature-overviews/purchasing-control-feature-overview.html).

## Install feature core

Follow the steps below to install the Purchasing Control feature core.

### Prerequisites

To start feature integration, review and install the necessary features:

| NAME | VERSION | INSTALLATION GUIDE |
| --- | --- | --- |
| Spryker Core | {{page.release_tag}} | [Install the Spryker Core feature](/docs/pbc/all/miscellaneous/latest/install-and-upgrade/install-features/install-the-spryker-core-feature.html) |
| Company Account | {{page.release_tag}} | [Install the Company Account feature](/docs/pbc/all/customer-relationship-management/latest/base-shop/install-and-upgrade/install-features/install-the-company-account-feature.html) |
| Checkout | {{page.release_tag}} | [Install the Checkout feature](/docs/pbc/all/cart-and-checkout/latest/base-shop/install-and-upgrade/install-features/install-the-checkout-feature.html) |
| Approval Process | {{page.release_tag}} | [Install the Approval Process feature](/docs/pbc/all/cart-and-checkout/latest/base-shop/install-and-upgrade/install-features/install-the-approval-process-feature.html) |

### 1) Install the required modules

```bash
composer require spryker-feature/purchasing-control:&quot;^1.0.0&quot; spryker/sales:&quot;^11.83.0&quot; spryker/sales-extension:&quot;^1.15.0&quot; spryker-shop/checkout-page:&quot;^3.40.0&quot; spryker-shop/company-page:&quot;^2.36.0&quot; spryker-shop/customer-page:&quot;^2.77.0&quot; spryker-shop/shop-ui:&quot;^1.108.0&quot; --update-with-dependencies --ignore-platform-req=ext-grpc
```

### 2) Set up database schema and transfer objects

Apply database changes and generate entity and transfer changes:

```bash
console propel:install
console transfer:generate
```

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

Make sure the following changes have been applied in the database:

| DATABASE ENTITY | TYPE | EVENT |
| --- | --- | --- |
| spy_cost_center | table | created |
| spy_cost_center_to_company_business_unit | table | created |
| spy_budget | table | created |
| spy_budget_consumption | table | created |
| spy_quote.fk_cost_center | column | created |
| spy_quote.fk_budget | column | created |
| spy_sales_order.fk_cost_center | column | created |
| spy_sales_order.fk_budget | column | created |

Make sure the following changes have been applied in transfer objects:

| TRANSFER | TYPE | EVENT | PATH |
| --- | --- | --- | --- |
| CostCenter | class | created | src/Generated/Shared/Transfer/CostCenterTransfer.php |
| CostCenterCollection | class | created | src/Generated/Shared/Transfer/CostCenterCollectionTransfer.php |
| CostCenterCriteria | class | created | src/Generated/Shared/Transfer/CostCenterCriteriaTransfer.php |
| CostCenterConditions | class | created | src/Generated/Shared/Transfer/CostCenterConditionsTransfer.php |
| CostCenterCollectionRequest | class | created | src/Generated/Shared/Transfer/CostCenterCollectionRequestTransfer.php |
| CostCenterCollectionResponse | class | created | src/Generated/Shared/Transfer/CostCenterCollectionResponseTransfer.php |
| CostCenterResponse | class | created | src/Generated/Shared/Transfer/CostCenterResponseTransfer.php |
| CostCenterQuoteUpdateRequest | class | created | src/Generated/Shared/Transfer/CostCenterQuoteUpdateRequestTransfer.php |
| CostCenterQuoteUpdateResponse | class | created | src/Generated/Shared/Transfer/CostCenterQuoteUpdateResponseTransfer.php |
| Budget | class | created | src/Generated/Shared/Transfer/BudgetTransfer.php |
| BudgetCollection | class | created | src/Generated/Shared/Transfer/BudgetCollectionTransfer.php |
| BudgetCriteria | class | created | src/Generated/Shared/Transfer/BudgetCriteriaTransfer.php |
| BudgetConditions | class | created | src/Generated/Shared/Transfer/BudgetConditionsTransfer.php |
| BudgetCollectionRequest | class | created | src/Generated/Shared/Transfer/BudgetCollectionRequestTransfer.php |
| BudgetCollectionResponse | class | created | src/Generated/Shared/Transfer/BudgetCollectionResponseTransfer.php |
| BudgetResponse | class | created | src/Generated/Shared/Transfer/BudgetResponseTransfer.php |
| BudgetConsumption | class | created | src/Generated/Shared/Transfer/BudgetConsumptionTransfer.php |
| BudgetConsumptionCollection | class | created | src/Generated/Shared/Transfer/BudgetConsumptionCollectionTransfer.php |
| BudgetConsumptionCriteria | class | created | src/Generated/Shared/Transfer/BudgetConsumptionCriteriaTransfer.php |
| BudgetConsumptionConditions | class | created | src/Generated/Shared/Transfer/BudgetConsumptionConditionsTransfer.php |
| Quote.idCostCenter | property | created | src/Generated/Shared/Transfer/QuoteTransfer.php |
| Quote.idBudget | property | created | src/Generated/Shared/Transfer/QuoteTransfer.php |
| Quote.costCenter | property | created | src/Generated/Shared/Transfer/QuoteTransfer.php |
| Quote.budget | property | created | src/Generated/Shared/Transfer/QuoteTransfer.php |
| Order.fkCostCenter | property | created | src/Generated/Shared/Transfer/OrderTransfer.php |
| Order.fkBudget | property | created | src/Generated/Shared/Transfer/OrderTransfer.php |
| Order.costCenter | property | created | src/Generated/Shared/Transfer/OrderTransfer.php |
| Order.budget | property | created | src/Generated/Shared/Transfer/OrderTransfer.php |
| OrderTableCriteria.costCenterIds | property | created | src/Generated/Shared/Transfer/OrderTableCriteriaTransfer.php |
| OrderTableCriteria.budgetIds | property | created | src/Generated/Shared/Transfer/OrderTableCriteriaTransfer.php |

{% endinfo_block %}

### 3) Set up data import

Register the following data import plugins:

| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
| --- | --- | --- | --- |
| CostCenterDataImportPlugin | Imports cost centers from `cost_center.csv`. Creates or updates cost centers by key, name, description, and active status. | None | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\DataImport |
| BudgetDataImportPlugin | Imports budgets from `budget.csv`. Resolves the cost center by key and creates or updates budgets by cost center and name. | None | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\DataImport |
| CostCenterToCompanyBusinessUnitDataImportPlugin | Imports cost center to company business unit relations from `cost_center_company_business_unit.csv`. Skips already existing relations. | None | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\DataImport |

**src/Pyz/Zed/DataImport/DataImportDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Zed\DataImport;

use Spryker\Zed\DataImport\DataImportDependencyProvider as SprykerDataImportDependencyProvider;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\DataImport\BudgetDataImportPlugin;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\DataImport\CostCenterDataImportPlugin;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\DataImport\CostCenterToCompanyBusinessUnitDataImportPlugin;

class DataImportDependencyProvider extends SprykerDataImportDependencyProvider
{
    /**
     * @return array&lt;\Spryker\Zed\DataImport\Dependency\Plugin\DataImportPluginInterface&gt;
     */
    protected function getDataImporterPlugins(): array
    {
        return [
            // ...
            new CostCenterDataImportPlugin(), #PurchasingControlFeature
            new BudgetDataImportPlugin(), #PurchasingControlFeature
            new CostCenterToCompanyBusinessUnitDataImportPlugin(), #PurchasingControlFeature
        ];
    }
}
```

Create the CSV import files:

**data/import/common/common/cost_center.csv**

```csv
key,name,description,is_active
cc-marketing,Marketing,Marketing and communications expenses,1
cc-it,IT &amp; Operations,IT infrastructure and software licenses,1
```

**data/import/common/common/budget.csv**

```csv
cost_center_key,name,amount,currency_iso_code,starts_at,ends_at,enforcement_rule,is_active
cc-marketing,Marketing Q2 2026,50000,EUR,2026-04-01,2026-06-30,warn,1
cc-it,IT Software Licenses 2026,100000,EUR,2026-01-01,2026-12-31,block,1
cc-it,IT Hardware Approvals 2026,200000,EUR,2026-01-01,2026-12-31,require_approval,1
```

**data/import/common/common/cost_center_company_business_unit.csv**

```csv
cost_center_key,business_unit_key
cc-marketing,spryker_systems_berlin
cc-it,spryker_systems_berlin
```

Import the data:

```bash
console data:import purchasing-control-cost-center
console data:import purchasing-control-budget
console data:import purchasing-control-cost-center-to-company-business-unit
```

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

In the Back Office, under **Customers &gt; Cost Centers**, make sure the imported cost centers and budgets are displayed. Make sure the cost centers are assigned to the expected business units.

{% endinfo_block %}

### 4) Set up behavior

Enable the following behaviors by registering the plugins.

#### Set up Zed plugins

| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
| --- | --- | --- | --- |
| ManageCostCentersPermissionPlugin | Grants permission to create, update, and manage cost centers. Assign this permission to company roles that should have access to Purchasing Control management pages. | None | SprykerFeature\Shared\PurchasingControl\Plugin\Permission |
| BudgetCheckoutPreConditionPlugin | Validates the cart grand total against the remaining budget before checkout proceeds. Blocks checkout or triggers the approval flow depending on the budget enforcement rule. | None | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Checkout |
| CostCenterOrderSaverPlugin | Saves the selected cost center and budget references to the sales order during checkout. | None | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Checkout |
| ConsumeBudgetCheckoutPostSavePlugin | Records budget consumption immediately after the order is saved so the remaining budget balance is accurate for concurrent buyers. Does nothing when no budget is selected on the quote. | None | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Checkout |
| CostCenterQuoteExpanderPlugin | Expands the quote with the default cost center assigned to the buyer&apos;s business unit when no cost center is already set. | None | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Quote |
| CostCenterQuoteFieldsAllowedForSavingProviderPlugin | Adds `idCostCenter` and `idBudget` to the list of quote fields persisted to the database. | None | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Quote |
| RestoreBudgetOnCancelOmsCommandPlugin | Restores the budget balance by deducting the amount of the canceled order items. Also deducts the shipment group total if all items in the group are canceled. | None | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Oms |
| RestoreBudgetOnRefundOmsCommandPlugin | Restores the budget balance by deducting the refundable amount of the refunded order items. When refund with shipment is enabled, also deducts the shipment group expense refundable amount if all items in the group are refunded. | None | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Oms |
| CostCenterOrderExpanderPlugin | Expands `OrderTransfer` with the assigned cost center and company name, and with the assigned budget when present. Does nothing when no cost center is assigned to the order. | None | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Sales |
| CostCenterSearchOrderExpanderPlugin | Expands each `OrderTransfer` in a list with `CostCenterTransfer` and `BudgetTransfer` when assigned. Used for order search results. | None | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Sales |
| CostCenterOrderSearchQueryExpanderPlugin | Expands `QueryJoinCollectionTransfer` with `WHERE` conditions for `fk_cost_center` and `fk_budget` when filter fields of type `costCenter` or `budget` are present. | None | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Sales |
| CostCenterOrdersTableQueryExpanderPlugin | Adds a `LEFT JOIN` from `spy_sales_order.fk_cost_center` to `spy_cost_center.id_cost_center` and exposes `cost_center_name` as a virtual column on the Back Office orders table query. | None | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Sales |
| CostCenterOrdersTableHeaderExpanderPlugin | Inserts a **Cost Center** column before the **Actions** column in the Back Office orders table. | None | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Sales |
| CostCenterOrdersTableFilterFormExpanderPlugin | Adds cost center and budget multi-select filter fields to the Back Office orders table filter form. Budget choices are loaded via AJAX filtered by the selected cost centers. | None | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Sales |
| CostCenterOrdersTableCriteriaFilterExpanderPlugin | Filters the Back Office orders table by `costCenterIds` and `budgetIds` when present on `OrderTableCriteriaTransfer`. | None | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Sales |
| CostCenterSalesTablePlugin | Normalizes the `cost_center_name` column to `-` for orders that have no cost center assigned. | None | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Sales |

#### Set up permissions

**src/Pyz/Zed/Permission/PermissionDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Zed\Permission;

use Spryker\Zed\Permission\PermissionDependencyProvider as SprykerPermissionDependencyProvider;
use SprykerFeature\Shared\PurchasingControl\Plugin\Permission\ManageCostCentersPermissionPlugin;

class PermissionDependencyProvider extends SprykerPermissionDependencyProvider
{
    /**
     * @return array&lt;\Spryker\Shared\PermissionExtension\Dependency\Plugin\PermissionPluginInterface&gt;
     */
    protected function getPermissionPlugins(): array
    {
        return [
            // ...
            new ManageCostCentersPermissionPlugin(), #PurchasingControlFeature
        ];
    }
}
```

**src/Pyz/Client/Permission/PermissionDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Client\Permission;

use Spryker\Client\Permission\PermissionDependencyProvider as SprykerPermissionDependencyProvider;
use SprykerFeature\Shared\PurchasingControl\Plugin\Permission\ManageCostCentersPermissionPlugin;

class PermissionDependencyProvider extends SprykerPermissionDependencyProvider
{
    /**
     * @return array&lt;\Spryker\Shared\PermissionExtension\Dependency\Plugin\PermissionPluginInterface&gt;
     */
    protected function getPermissionPlugins(): array
    {
        return [
            // ...
            new ManageCostCentersPermissionPlugin(), #PurchasingControlFeature
        ];
    }
}
```

Sync the permission plugins to the database:

```bash
console sync:data permission
```

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

In the Back Office, under **Customers &gt; Company Roles**, assign the **ManageCostCentersPermissionPlugin** permission to a company role. Make sure company users with that role can access the cost center management pages on the Storefront.

{% endinfo_block %}

{% info_block infoBox &quot;Require Approval enforcement rule&quot; %}

If you configure budgets with the **Require Approval** enforcement rule, the following [Approval Process](/docs/pbc/all/cart-and-checkout/latest/base-shop/install-and-upgrade/install-features/install-the-approval-process-feature.html) permissions must be registered and assigned to company roles for the approval workflow to function:

| PERMISSION | REQUIRES |
| --- | --- |
| Buy up to grand total (`PlaceOrderPermissionPlugin`) | Send cart for approval |
| Send cart for approval (`RequestQuoteApprovalPermissionPlugin`) | Buy up to grand total |
| Approve up to grand total (`ApproveQuotePermissionPlugin`) | None |

For plugin registration details, see [Install the Approval Process feature](/docs/pbc/all/cart-and-checkout/latest/base-shop/install-and-upgrade/install-features/install-the-approval-process-feature.html).

{% endinfo_block %}

#### Set up Checkout plugins

**src/Pyz/Zed/Checkout/CheckoutDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Zed\Checkout;

use Spryker\Zed\Checkout\CheckoutDependencyProvider as SprykerCheckoutDependencyProvider;
use Spryker\Zed\Kernel\Container;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Checkout\BudgetCheckoutPreConditionPlugin;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Checkout\ConsumeBudgetCheckoutPostSavePlugin;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Checkout\CostCenterOrderSaverPlugin;

class CheckoutDependencyProvider extends SprykerCheckoutDependencyProvider
{
    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return list&lt;\Spryker\Zed\CheckoutExtension\Dependency\Plugin\CheckoutPreConditionPluginInterface&gt;
     */
    protected function getCheckoutPreConditions(Container $container): array
    {
        return [
            // ...
            new BudgetCheckoutPreConditionPlugin(), #PurchasingControlFeature
        ];
    }

    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return list&lt;\Spryker\Zed\Checkout\Dependency\Plugin\CheckoutSaveOrderInterface|\Spryker\Zed\CheckoutExtension\Dependency\Plugin\CheckoutDoSaveOrderInterface&gt;
     */
    protected function getCheckoutOrderSavers(Container $container): array
    {
        return [
            // ...
            new CostCenterOrderSaverPlugin(), #PurchasingControlFeature
        ];
    }

    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return list&lt;\Spryker\Zed\CheckoutExtension\Dependency\Plugin\CheckoutPostSaveInterface&gt;
     */
    protected function getCheckoutPostHooks(Container $container): array
    {
        return [
            // ...
            new ConsumeBudgetCheckoutPostSavePlugin(), #PurchasingControlFeature
        ];
    }
}
```

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

When a buyer places an order with a budget selected, verify the following:
- Checkout is blocked when the order exceeds a budget with the **Block** enforcement rule.
- An approval request is triggered when the order exceeds a budget with the **Require Approval** rule.
- A warning is displayed when the order exceeds a budget with the **Warn** rule.
- A `spy_budget_consumption` record is created after the order is successfully placed.
- The cost center and budget IDs are saved on the `spy_sales_order` record.

{% endinfo_block %}

#### Set up Quote plugins

**src/Pyz/Zed/Quote/QuoteDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Zed\Quote;

use Spryker\Zed\Quote\QuoteDependencyProvider as SprykerQuoteDependencyProvider;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Quote\CostCenterQuoteExpanderPlugin;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Quote\CostCenterQuoteFieldsAllowedForSavingProviderPlugin;

class QuoteDependencyProvider extends SprykerQuoteDependencyProvider
{
    /**
     * @return array&lt;\Spryker\Zed\QuoteExtension\Dependency\Plugin\QuoteExpanderPluginInterface&gt;
     */
    protected function getQuoteExpanderPlugins(): array
    {
        return [
            // ...
            new CostCenterQuoteExpanderPlugin(), #PurchasingControlFeature
        ];
    }

    /**
     * @return array&lt;\Spryker\Zed\QuoteExtension\Dependency\Plugin\QuoteFieldsAllowedForSavingProviderPluginInterface&gt;
     */
    protected function getQuoteFieldsAllowedForSavingProviderPlugins(): array
    {
        return [
            // ...
            new CostCenterQuoteFieldsAllowedForSavingProviderPlugin(), #PurchasingControlFeature
        ];
    }
}
```

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

When a buyer with an assigned business unit opens a cart, make sure the quote is automatically expanded with the default cost center of their business unit.

Make sure `idCostCenter` and `idBudget` are persisted to the `spy_quote` table when the quote is saved.

{% endinfo_block %}

#### Set up Sales plugins

**src/Pyz/Zed/Sales/SalesDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Zed\Sales;

use Spryker\Zed\Sales\SalesDependencyProvider as SprykerSalesDependencyProvider;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Sales\CostCenterOrderExpanderPlugin;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Sales\CostCenterOrderSearchQueryExpanderPlugin;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Sales\CostCenterOrdersTableCriteriaFilterExpanderPlugin;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Sales\CostCenterOrdersTableFilterFormExpanderPlugin;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Sales\CostCenterOrdersTableHeaderExpanderPlugin;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Sales\CostCenterOrdersTableQueryExpanderPlugin;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Sales\CostCenterSalesTablePlugin;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Sales\CostCenterSearchOrderExpanderPlugin;

class SalesDependencyProvider extends SprykerSalesDependencyProvider
{
    /**
     * @return array&lt;\Spryker\Zed\Sales\Dependency\Plugin\OrderExpanderPreSavePluginInterface&gt;
     */
    protected function getOrderHydrationPlugins(): array
    {
        return [
            // ...
            new CostCenterOrderExpanderPlugin(), #PurchasingControlFeature
        ];
    }

    /**
     * @return array&lt;\Spryker\Zed\SalesExtension\Dependency\Plugin\SearchOrderExpanderPluginInterface&gt;
     */
    protected function getSearchOrderExpanderPlugins(): array
    {
        return [
            // ...
            new CostCenterSearchOrderExpanderPlugin(), #PurchasingControlFeature
        ];
    }

    /**
     * @return array&lt;\Spryker\Zed\SalesExtension\Dependency\Plugin\SearchOrderQueryExpanderPluginInterface&gt;
     */
    protected function getOrderSearchQueryExpanderPlugins(): array
    {
        return [
            // ...
            new CostCenterOrderSearchQueryExpanderPlugin(), #PurchasingControlFeature
        ];
    }

    /**
     * @return array&lt;\Spryker\Zed\SalesExtension\Dependency\Plugin\OrdersTableQueryExpanderPluginInterface&gt;
     */
    protected function getOrdersTableQueryExpanderPlugins(): array
    {
        return [
            // ...
            new CostCenterOrdersTableQueryExpanderPlugin(), #PurchasingControlFeature
        ];
    }

    /**
     * @return array&lt;\Spryker\Zed\SalesExtension\Dependency\Plugin\OrdersTableHeaderExpanderPluginInterface&gt;
     */
    protected function getOrdersTableHeaderExpanderPlugins(): array
    {
        return [
            // ...
            new CostCenterOrdersTableHeaderExpanderPlugin(), #PurchasingControlFeature
        ];
    }

    /**
     * @return array&lt;\Spryker\Zed\SalesExtension\Dependency\Plugin\OrdersTableFilterFormExpanderPluginInterface&gt;
     */
    protected function getOrdersTableFilterFormExpanderPlugins(): array
    {
        return [
            // ...
            new CostCenterOrdersTableFilterFormExpanderPlugin(), #PurchasingControlFeature
        ];
    }

    /**
     * @return array&lt;\Spryker\Zed\SalesExtension\Dependency\Plugin\OrdersTableCriteriaFilterExpanderPluginInterface&gt;
     */
    protected function getOrdersTableCriteriaFilterExpanderPlugins(): array
    {
        return [
            // ...
            new CostCenterOrdersTableCriteriaFilterExpanderPlugin(), #PurchasingControlFeature
        ];
    }

    /**
     * @return array&lt;\Spryker\Zed\SalesExtension\Dependency\Plugin\SalesTablePluginInterface&gt;
     */
    protected function getSalesTablePlugins(): array
    {
        return [
            // ...
            new CostCenterSalesTablePlugin(), #PurchasingControlFeature
        ];
    }
}
```

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

- In the Back Office, open **Sales &gt; Orders**. Make sure the **Cost Center** column is displayed in the orders table.
- Make sure the orders table filter form includes cost center and budget multi-select fields.
- Open an individual order. Make sure the cost center and budget names are displayed on the order detail page.
- In the storefront, open **My Account &gt; Orders**. Make sure the cost center and budget data appear on completed orders.

{% endinfo_block %}

### 5) Configure Back Office navigation

Add the Purchasing Control section to the Back Office navigation:

**config/Zed/navigation.xml**

```xml
&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;config&gt;
    &lt;customer&gt;
        ...
        &lt;pages&gt;
            ...
            &lt;purchasing-control&gt;
                &lt;label&gt;Cost Centers&lt;/label&gt;
                &lt;title&gt;Cost Centers&lt;/title&gt;
                &lt;bundle&gt;purchasing-control&lt;/bundle&gt;
                &lt;controller&gt;cost-center&lt;/controller&gt;
                &lt;action&gt;index&lt;/action&gt;
            &lt;/purchasing-control&gt;
        &lt;/pages&gt;
    &lt;/customer&gt;
&lt;/config&gt;
```

Rebuild the navigation cache:

```bash
console navigation:build-cache
```

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

In the Back Office, under **Customers**, make sure the **Cost Centers** menu item is displayed and links to the cost center list page.

{% endinfo_block %}

### 6) Configure the OMS process

Register the OMS command plugins and configure the OMS process XML.

#### Register OMS command plugins

**src/Pyz/Zed/Oms/OmsDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Zed\Oms;

use Spryker\Zed\Kernel\Container;
use Spryker\Zed\Oms\Dependency\Plugin\Command\CommandCollectionInterface;
use Spryker\Zed\Oms\OmsDependencyProvider as SprykerOmsDependencyProvider;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Oms\RestoreBudgetOnCancelOmsCommandPlugin;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\Oms\RestoreBudgetOnRefundOmsCommandPlugin;

class OmsDependencyProvider extends SprykerOmsDependencyProvider
{
    /**
     * @param \Spryker\Zed\Kernel\Container $container
     *
     * @return \Spryker\Zed\Kernel\Container
     */
    protected function extendCommandPlugins(Container $container): Container
    {
        $container-&gt;extend(self::COMMAND_PLUGINS, function (CommandCollectionInterface $commandCollection) {
            // ...
            $commandCollection-&gt;add(new RestoreBudgetOnCancelOmsCommandPlugin(), &apos;CostCenter/RestoreBudgetOnCancel&apos;); #PurchasingControlFeature
            $commandCollection-&gt;add(new RestoreBudgetOnRefundOmsCommandPlugin(), &apos;CostCenter/RestoreBudgetOnRefund&apos;); #PurchasingControlFeature

            return $commandCollection;
        });

        return $container;
    }
}
```

#### Configure the OMS process XML

Add the `CostCenter/RestoreBudgetOnCancel` and `CostCenter/RestoreBudgetOnRefund` commands to the relevant events in your OMS process XML. The following example uses `DummyPayment01`:

**config/Zed/oms/DummyPayment01.xml**

```xml
&lt;events&gt;
    ...
    &lt;event name=&quot;cancel&quot; manual=&quot;true&quot; command=&quot;CostCenter/RestoreBudgetOnCancel&quot;/&gt;
    &lt;event name=&quot;refund&quot; manual=&quot;true&quot; command=&quot;CostCenter/RestoreBudgetOnRefund&quot;/&gt;
    ...
&lt;/events&gt;
```

#### Configure budget restoration behavior

By default, shipment costs are not included when restoring the budget on refund. To include shipment costs, override `isRefundWithShipmentEnabled()` in your project config:

**src/Pyz/Zed/PurchasingControl/PurchasingControlConfig.php**

```php
&lt;?php

namespace Pyz\Zed\PurchasingControl;

use SprykerFeature\Zed\PurchasingControl\PurchasingControlConfig as SprykerPurchasingControlConfig;

class PurchasingControlConfig extends SprykerPurchasingControlConfig
{
    protected const bool REFUND_WITH_SHIPMENT_ENABLED = true;
}
```

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

- Place an order with a budget selected. Cancel one item (partial cancel). Make sure the budget balance is increased by the amount of the canceled item only, not the full order total.
- Cancel all items of an order. Make sure the full consumed amount is restored to the budget balance.
- Trigger a refund on an order. Make sure the refunded items&apos; amounts are restored to the budget balance.

{% endinfo_block %}

## Install feature frontend

Follow the steps below to install the Purchasing Control feature frontend.

### 1) Import data

Import the following glossary keys for Storefront translations:

**data/import/common/common/glossary.csv**

```csv
purchasing_control.selector.placeholder,Select cost center,en_US
purchasing_control.selector.placeholder,Kostenstelle wählen,de_DE
purchasing_control.budget.selector.label,Budget,en_US
purchasing_control.budget.selector.label,Budget,de_DE
purchasing_control.budget.selector.placeholder,Select budget,en_US
purchasing_control.budget.selector.placeholder,Budget wählen,de_DE
purchasing_control.budget.remaining,Remaining budget,en_US
purchasing_control.budget.remaining,Verbleibendes Budget,de_DE
purchasing_control.summary.cost_center_label,Cost Center,en_US
purchasing_control.summary.cost_center_label,Kostenstelle,de_DE
purchasing_control.summary.budget_label,Budget,en_US
purchasing_control.summary.budget_label,Budget,de_DE
purchasing_control.summary.budget_remaining,remaining,en_US
purchasing_control.summary.budget_remaining,verbleibend,de_DE
purchasing_control.validation.block,&quot;Your order exceeds the allocated budget. Please adjust your order or contact your manager.&quot;,en_US
purchasing_control.validation.block,&quot;Ihre Bestellung überschreitet das zugewiesene Budget. Bitte passen Sie Ihre Bestellung an oder kontaktieren Sie Ihren Manager.&quot;,de_DE
purchasing_control.validation.warn,Your order exceeds the allocated budget.,en_US
purchasing_control.validation.warn,Ihre Bestellung überschreitet das zugewiesene Budget.,de_DE
purchasing_control.validation.require-approval,This order exceeds the budget. Please send it for approval.,en_US
purchasing_control.validation.require-approval,Diese Bestellung überschreitet das Budget. Bitte senden Sie sie zur Genehmigung.,de_DE
purchasing_control.validation.required,&quot;Please select a cost center and budget before placing your order.&quot;,en_US
purchasing_control.validation.required,&quot;Bitte wählen Sie vor der Bestellung eine Kostenstelle und ein Budget aus.&quot;,de_DE
```

Import data:

```bash
console data:import:glossary
```

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

Make sure that, in the database, the configured data has been added to the `spy_glossary_key` and `spy_glossary_translation` tables.

{% endinfo_block %}

### 2) Set up widgets

Register the following global widgets:

| WIDGET | DESCRIPTION | NAMESPACE |
| --- | --- | --- |
| PurchasingControlSummaryWidget | Displays cost center count and budget summaries on the company dashboard. | SprykerFeature\Yves\PurchasingControl\Widget |
| CostCenterSelectorWidget | Renders the cost center and budget selection UI at checkout. | SprykerFeature\Yves\PurchasingControl\Widget |
| CostCenterMenuItemWidget | Renders the Purchasing Control navigation menu item in the storefront company menu. | SprykerFeature\Yves\PurchasingControl\Widget |
| CostCenterBudgetFilterWidget | Renders the cost center and budget filter controls on the order history page. | SprykerFeature\Yves\PurchasingControl\Widget |
| CostCenterOrderDetailWidget | Displays the assigned cost center and budget on the order detail page, taking an `OrderTransfer` as input. | SprykerFeature\Yves\PurchasingControl\Widget |
| CostCenterDetailWidget | Displays the assigned cost center and budget on the cart or quote detail page, taking a `QuoteTransfer` as input. | SprykerFeature\Yves\PurchasingControl\Widget |

**src/Pyz/Yves/ShopApplication/ShopApplicationDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Yves\ShopApplication;

use SprykerFeature\Yves\PurchasingControl\Widget\CostCenterBudgetFilterWidget;
use SprykerFeature\Yves\PurchasingControl\Widget\CostCenterDetailWidget;
use SprykerFeature\Yves\PurchasingControl\Widget\CostCenterMenuItemWidget;
use SprykerFeature\Yves\PurchasingControl\Widget\CostCenterOrderDetailWidget;
use SprykerFeature\Yves\PurchasingControl\Widget\CostCenterSelectorWidget;
use SprykerFeature\Yves\PurchasingControl\Widget\PurchasingControlSummaryWidget;
use SprykerShop\Yves\ShopApplication\ShopApplicationDependencyProvider as SprykerShopApplicationDependencyProvider;

class ShopApplicationDependencyProvider extends SprykerShopApplicationDependencyProvider
{
    /**
     * @return array&lt;string&gt;
     */
    protected function getGlobalWidgets(): array
    {
        return [
            // ...
            CostCenterMenuItemWidget::class, #PurchasingControlFeature
            PurchasingControlSummaryWidget::class, #PurchasingControlFeature
            CostCenterSelectorWidget::class, #PurchasingControlFeature
            CostCenterDetailWidget::class, #PurchasingControlFeature
            CostCenterOrderDetailWidget::class, #PurchasingControlFeature
            CostCenterBudgetFilterWidget::class, #PurchasingControlFeature
        ];
    }
}
```

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

- Make sure all six widgets are available in Twig templates.
- On the storefront company dashboard, make sure the **Purchasing Control** summary widget displays cost center and budget data.
- On the checkout summary page, make sure the cost center and budget selector is displayed.
- On the order detail page, make sure the assigned cost center and budget names are displayed.
- On the order history page, make sure the cost center and budget filter controls are displayed.

{% endinfo_block %}

### 3) Extend the ShopUi select component

Extend the ShopUi `select` atom to render HTML attributes for the cost center and budget selectors:

**src/Pyz/Yves/ShopUi/Theme/default/components/atoms/select/select.twig**

```twig
{% raw %}{% block attributes %}
    {%- for attrname, attrvalue in attr | default({}) -%}
        {%- if attrvalue is same as(true) -%} {{ attrname }}=&quot;{{ attrname }}&quot;
        {%- elseif attrvalue is not same as(false) -%} {{ attrname }}=&quot;{{ attrvalue }}&quot;
        {%- endif -%}
    {%- endfor -%}
{% endblock %}{% endraw %}
```

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

Make sure the cost center and budget dropdowns correctly disable unavailable options.

{% endinfo_block %}

### 4) Extend the checkout summary template

Add the `CostCenterSelectorWidget` to the checkout summary page, placing it directly above the `QuoteApprovalWidget` call:

**src/SprykerShop/CheckoutPage/src/SprykerShop/Yves/CheckoutPage/Theme/default/views/summary/summary.twig**

```twig
&lt;div class=&quot;box&quot;&gt;
    {% raw %}{% widget &apos;CostCenterSelectorWidget&apos; args [data.cart] only %}{% endwidget %}{% endraw %}
    {% raw %}{% widget &apos;QuoteApprovalWidget&apos; args [data.cart] only %}{% endwidget %}{% endraw %}
&lt;/div&gt;
```

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

On the checkout summary page, make sure the cost center and budget selector is displayed above the approval widget.

{% endinfo_block %}

### 5) Set up routes

Register the following route provider plugins:

| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
| --- | --- | --- | --- |
| CostCenterRouteProviderPlugin | Adds storefront routes for cost center list, create, update, and quote update actions. | None | SprykerFeature\Yves\PurchasingControl\Plugin\Router |
| BudgetRouteProviderPlugin | Adds storefront routes for budget list, create, and update actions. | None | SprykerFeature\Yves\PurchasingControl\Plugin\Router |

**src/Pyz/Yves/Router/RouterDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Yves\Router;

use Spryker\Yves\Router\RouterDependencyProvider as SprykerRouterDependencyProvider;
use SprykerFeature\Yves\PurchasingControl\Plugin\Router\BudgetRouteProviderPlugin;
use SprykerFeature\Yves\PurchasingControl\Plugin\Router\CostCenterRouteProviderPlugin;

class RouterDependencyProvider extends SprykerRouterDependencyProvider
{
    /**
     * @return array&lt;\Spryker\Yves\RouterExtension\Dependency\Plugin\RouteProviderPluginInterface&gt;
     */
    protected function getRouteProvider(): array
    {
        return [
            // ...
            new CostCenterRouteProviderPlugin(), #PurchasingControlFeature
            new BudgetRouteProviderPlugin(), #PurchasingControlFeature
        ];
    }
}
```

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

- Make sure the cost center list, create, and update pages are accessible under `/company/cost-center`.
- Make sure submitting the cost center selector form in the cart updates the quote with the selected cost center and budget.
- Make sure the budget list, create, and update pages are accessible.

{% endinfo_block %}

### 6) Extend the order search form

Register the following plugins to add cost center and budget filter fields to the storefront order history search form:

| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
| --- | --- | --- | --- |
| CostCenterOrderSearchFormExpanderPlugin | Adds cost center and budget filter dropdowns to the order history search form. Only adds fields when the current customer is a company user. | None | SprykerFeature\Yves\PurchasingControl\Plugin\CustomerPage |
| CostCenterOrderSearchFormHandlerPlugin | Maps selected cost center and budget IDs from the filter form to `FilterFieldTransfer` entries on `OrderListTransfer`. | None | SprykerFeature\Yves\PurchasingControl\Plugin\CustomerPage |

**src/Pyz/Yves/CustomerPage/CustomerPageDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Yves\CustomerPage;

use SprykerShop\Yves\CustomerPage\CustomerPageDependencyProvider as SprykerShopCustomerPageDependencyProvider;
use SprykerFeature\Yves\PurchasingControl\Plugin\CustomerPage\CostCenterOrderSearchFormExpanderPlugin;
use SprykerFeature\Yves\PurchasingControl\Plugin\CustomerPage\CostCenterOrderSearchFormHandlerPlugin;

class CustomerPageDependencyProvider extends SprykerShopCustomerPageDependencyProvider
{
    /**
     * @return array&lt;\SprykerShop\Yves\CustomerPageExtension\Dependency\Plugin\OrderSearchFormExpanderPluginInterface&gt;
     */
    protected function getOrderSearchFormExpanderPlugins(): array
    {
        return [
            // ...
            new CostCenterOrderSearchFormExpanderPlugin(), #PurchasingControlFeature
        ];
    }

    /**
     * @return array&lt;\SprykerShop\Yves\CustomerPageExtension\Dependency\Plugin\OrderSearchFormHandlerPluginInterface&gt;
     */
    protected function getOrderSearchFormHandlerPlugins(): array
    {
        return [
            // ...
            new CostCenterOrderSearchFormHandlerPlugin(), #PurchasingControlFeature
        ];
    }
}
```

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

On the storefront **My Account &gt; Orders** page, make sure company users see cost center and budget filter dropdowns. Make sure filtering by cost center or budget returns the expected orders.

{% endinfo_block %}
</description>
            <pubDate>Tue, 30 Jun 2026 11:01:37 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/cart-and-checkout/latest/base-shop/install-and-upgrade/install-features/install-the-purchasing-control-feature.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/cart-and-checkout/latest/base-shop/install-and-upgrade/install-features/install-the-purchasing-control-feature.html</guid>
            
            
        </item>
        
        <item>
            <title>Security release notes 202606.0</title>
            <description>This document describes the security-related issues that have been recently resolved.

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

## Information disclosure via phpinfo() method

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

This security update requires [Spryker 202604.0](/docs/about/all/releases/release-notes-202604.0.html) or later. Ensure your project is upgraded to this version before applying the fix.

{% endinfo_block %}

Instances of phpinfo() were identified in the codebase, which could potentially expose sensitive configuration details and environment variables to unauthorized parties. Such an instance was found to be part of the default Back Office setup.

### Affected modules

- `spryker/setup`: &lt; 4.8.0
- `spryker/maintenance`: &lt; 3.6.0

### Fix the vulnerability

Update the `spryker/setup` package to version 4.8.0 or higher:

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

Update the `spryker/maintenance` package to version 4.0.0 or higher:

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


## Possible brute force attack in adding discount voucher / gift card codes

An automated attack could attempt to guess valid strings by using every possible combination and/or pre-defined dictionaries.
In the site frontend, there is the possibility to use a discount code (voucher) or a gift card code, which is a predefined or randomized string.

### Affected modules

- `spryker-shop/cart-code-widget`: &lt; 1.6.0

### Fix the vulnerability

Update the `spryker-shop/cart-code-widget` package to version 1.7.0 or higher:

```bash
composer update spryker-shop/cart-code-widget:&quot;^1.7.0&quot;
composer show spryker-shop/cart-code-widget # Verify the version
```


Enable `SecurityBlockerCartCodeEventDispatcherPlugin` plugin:

**src/Pyz/Yves/EventDispatcher/EventDispatcherDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Yves\EventDispatcher;

use Spryker\Yves\EventDispatcher\EventDispatcherDependencyProvider as SprykerEventDispatcherDependencyProvider;
use SprykerShop\Yves\CartCodeWidget\Plugin\EventDispatcher\SecurityBlockerCartCodeEventDispatcherPlugin;

class EventDispatcherDependencyProvider extends SprykerEventDispatcherDependencyProvider
{
    /**
     * @return list&lt;\Spryker\Shared\EventDispatcherExtension\Dependency\Plugin\EventDispatcherPluginInterface&gt;
     */
    protected function getEventDispatcherPlugins(): array
    {
        return [
            new SecurityBlockerCartCodeEventDispatcherPlugin(),
        ];
    }
}
```

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

1. From the cart page, submit an invalid voucher or gift card code multiple times.
2. After exceeding the configured number of attempts, make sure the request is blocked and the `cart_code_widget.error.too_many_requests` error message is displayed.

{% endinfo_block %}

Add glossary translations for the message `cart_code_widget.error.too_many_requests`.


## File enumeration via predictable file IDs in File Manager

Files stored through the File Manager module were referenced using sequential numeric IDs, making it possible to enumerate and access files by guessing IDs. Introducing UUID-based identifiers for file entities prevents unauthorized enumeration of file resources.

### Affected modules

- `spryker/file-manager`: &lt; 2.9.0
- `spryker/file-manager-storage`: &lt; 2.7.0
- `spryker-shop/content-file-widget`: &lt; 2.1.0
- `spryker-shop/file-manager-widget`: &lt; 2.1.0
- `spryker/synchronization-behavior`: &lt; 1.15.0
- `spryker/propel`: &lt; 3.50.1

### Fix the vulnerability

Update the affected packages:

```bash
composer update spryker/file-manager:&quot;^2.9.0&quot; spryker/file-manager-storage:&quot;^2.7.0&quot; spryker-shop/content-file-widget:&quot;^2.1.0&quot; spryker-shop/file-manager-widget:&quot;^2.1.0&quot; spryker/synchronization-behavior:&quot;^1.15.0&quot; spryker/propel:&quot;^3.50.1&quot; --update-with-dependencies
```

### Activate UUID for file entities

1. Enable UUID generation by overriding `FileManagerConfig::isUuidEnabled()` in your project configuration. By default, UUID generation is disabled.

**src/Pyz/Zed/FileManager/FileManagerConfig.php**

```php
&lt;?php

namespace Pyz\Zed\FileManager;

use Spryker\Zed\FileManager\FileManagerConfig as SprykerFileManagerConfig;

class FileManagerConfig extends SprykerFileManagerConfig
{
    /**
     * @return bool
     */
    public function isUuidEnabled(): bool
    {
        return true;
    }
}
```

2. Re-save all existing file entities to generate UUIDs for them. Make sure that the UUID field is populated for each file entity.

3. Rebuild the storage data:

```bash
console publish:trigger-events -r file
```


## PHP code injection via Twig template name

The `Compiler::string()` method in Twig failed to escape single quotes when generating PHP double-quoted string literals. An attacker could craft a template name containing a single quote to terminate the surrounding PHP string early, injecting arbitrary PHP expressions into the compiled Twig cache file. The injected code executes when the cache file is loaded, bypassing the Twig sandbox and enabling remote code execution. Because `SecurityPolicy` permits `{% raw %}{% use %}{% endraw %}` tags in sandboxed templates, this vulnerability is exploitable even in restricted environments.

### Affected modules

- `twig/twig`: &lt; 3.26.0
- `spryker/twig`: &lt; 3.31.0

### Fix the vulnerability

Update the affected packages:

```bash
composer update twig/twig:&quot;^3.26.0&quot; spryker/twig:&quot;^3.31.0&quot;
composer show twig/twig spryker/twig # Verify the versions
```


## DOM-based cross-site scripting (XSS) in Back Office JavaScript modules

Several Back Office JavaScript modules passed untrusted, unsanitized data directly into jQuery execution sinks, such as the `$()` constructor, `.append()`, `.html()`, or sensitive attributes like `href`. If the data contained malicious HTML or JavaScript, jQuery&apos;s internal engine evaluated and executed it, leading to DOM-based cross-site scripting (XSS).

### Affected modules

- `spryker/cms`: &lt; 7.20.1
- `spryker/cms-slot-block-gui`: &lt; 1.6.1
- `spryker/company-role-gui`: &lt; 1.11.1
- `spryker/content-gui`: &lt; 3.1.1
- `spryker/file-manager-gui`: &lt; 3.1.1
- `spryker/gui`: &lt; 5.3.1

### Fix the vulnerability

Update the affected packages:

```bash
composer update spryker/cms:&quot;^7.20.1&quot; spryker/cms-slot-block-gui:&quot;^1.6.1&quot; spryker/company-role-gui:&quot;^1.11.1&quot; spryker/content-gui:&quot;^3.1.1&quot; spryker/file-manager-gui:&quot;^3.1.1&quot; spryker/gui:&quot;^5.3.1&quot;
composer show spryker/cms spryker/cms-slot-block-gui spryker/company-role-gui spryker/content-gui spryker/file-manager-gui spryker/gui # Verify the versions
```

If you extended any of the affected JavaScript modules on the project level, never pass untrusted user input directly into the jQuery `$()` constructor or DOM manipulation methods. Use secure alternatives such as `.text()` or the native `textContent` property to render text. If you must render dynamic HTML, sanitize the input first with [DOMPurify](https://www.npmjs.com/package/dompurify).

1. Install the `dompurify` package:

```bash
npm install dompurify
```

2. Import `DOMPurify` and sanitize the untrusted data before passing it into a jQuery sink:

```js
import DOMPurify from &apos;dompurify&apos;;

// Before
$(container).html(untrustedData);

// After
$(container).html(DOMPurify.sanitize(untrustedData));
```

## Vulnerability in symfony third-party dependency

Multiple security vulnerabilities were identified in several Symfony third-party packages, potentially affecting application security, routing, email handling, and string processing.

### Affected modules

- `symfony/security-http`: &lt; 6.4.41
- `symfony/monolog-bridge`: 6.0.0 - 6.4.39
- `symfony/mailer`: 6.0.0 - 6.4.39
- `symfony/runtime`: 6.4.14 - 6.4.39
- `symfony/string`: 7.4.0 - 7.4.11
- `symfony/routing`: &lt; 6.4.41
- `symfony/mime`: 6.4.0 - 6.4.40

### Fix the vulnerability

Update the affected Symfony packages:

```bash
composer update symfony/security-http:&quot;^6.4.41&quot; symfony/monolog-bridge:&quot;^6.4.40&quot; symfony/mailer:&quot;^6.4.40&quot; symfony/runtime:&quot;^6.4.40&quot; symfony/routing:&quot;^6.4.41&quot; symfony/mime:&quot;^6.4.41&quot; symfony/string:&quot;^7.4.13&quot;
composer show symfony/security-http symfony/monolog-bridge symfony/mailer symfony/runtime symfony/routing symfony/mime symfony/string # Verify the versions
```
</description>
            <pubDate>Mon, 29 Jun 2026 13:23:46 +0000</pubDate>
            <link>https://docs.spryker.com/docs/about/all/releases/security-releases/security-release-notes-202606.0.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/about/all/releases/security-releases/security-release-notes-202606.0.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
```

## 8. Warm the API Platform router cache

API Platform registers its operations as routes in the standard Symfony router. With debug disabled (production), the compiled router dump is written once and then frozen for the life of the container, so it **must** be built against the fully generated resource collection. After generating resources (step 5), warm the cache for each Glue application:

```bash
docker/sdk cli GLUE_APPLICATION=GLUE_STOREFRONT glue cache:warmup
docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue cache:warmup
```

{% info_block warningBox &quot;Required on AWS and any multi-container deployment&quot; %}

This step is mandatory in cloud (AWS ECS) and any split build/runtime topology. If the router dump is built before resources are generated—or never warmed in the runtime container—every API request returns HTTP 404 and the empty dump stays frozen for the life of the container.

Use `cache:warmup` (or `cache:clear`), **not** `api:router:cache:warm-up`, which warms only the legacy Glue router and does not build the API Platform router dump.

For the full explanation, multi-container guidance, and recovery steps, see [Cache warming](/docs/dg/dev/architecture/api-platform.html#cache-warming) and [Multi-container and cloud deployments](/docs/dg/dev/architecture/api-platform.html#multi-container-and-cloud-deployments) in the architecture guide.

{% endinfo_block %}

## 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>Mon, 29 Jun 2026 11:21:50 +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</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;API Platform deployment requires two sequential steps, not alternatives:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Generate the API resource classes from the schema files:&lt;/li&gt;
&lt;/ol&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;ol start=&quot;2&quot;&gt;
&lt;li&gt;Warm the application cache—including the &lt;strong&gt;router cache&lt;/strong&gt;—once the resources from step 1 exist. Run it per Glue application:&lt;/li&gt;
&lt;/ol&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 &lt;span class=&quot;nv&quot;&gt;GLUE_APPLICATION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;GLUE_STOREFRONT glue cache:warmup
docker/sdk cli &lt;span class=&quot;nv&quot;&gt;GLUE_APPLICATION&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;GLUE_BACKEND glue cache:warmup
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;API Platform registers its operations as routes in the standard Symfony router, whose compiled matcher and generator are dumped to &lt;code&gt;data/cache/Glue&amp;lt;Storefront|Backend&amp;gt;/&amp;lt;environment&amp;gt;/url_matching_routes.php&lt;/code&gt; and &lt;code&gt;url_generating_routes.php&lt;/code&gt;. &lt;code&gt;cache:warmup&lt;/code&gt; builds these dumps from the resource collection produced in step 1. Add both steps to your deployment and installation recipes for every API Platform application.&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;Use cache:warmup, not api:router:cache:warm-up&lt;/div&gt;
&lt;p&gt;&lt;code&gt;api:router:cache:warm-up&lt;/code&gt; warms only the legacy Glue (&lt;code&gt;GlueApplication&lt;/code&gt;) custom-route router—it does &lt;strong&gt;not&lt;/strong&gt; build the API Platform router dump. Use &lt;code&gt;cache:warmup&lt;/code&gt; (or &lt;code&gt;cache:clear&lt;/code&gt;) to warm the API Platform router.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;h4 id=&quot;multi-container-and-cloud-deployments&quot;&gt;Multi-container and cloud deployments&lt;/h4&gt;
&lt;p&gt;In production, applications run with debug disabled. The router dump is then written once and never revalidated—whatever route set it was first built from is frozen for the life of the container.&lt;/p&gt;
&lt;p&gt;In a single-container setup this is harmless: the cache is warmed in the same place that serves requests, with the full route set. In a multi-container topology where resource generation runs in a build container and requests are served by a separate runtime container (for example, AWS ECS), you must guarantee the router dump is built against the complete resource collection &lt;strong&gt;for the runtime container&lt;/strong&gt;—either warmed in the runtime container after deployment, or baked at build time only if &lt;code&gt;data/cache&lt;/code&gt; is shipped to every runtime replica with the full route set.&lt;/p&gt;
&lt;p&gt;If the dump is built before resources are generated (an empty or incomplete collection), the runtime container freezes that empty dump and:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;every API request returns HTTP 404 (Glue code &lt;code&gt;007&lt;/code&gt;, legacy fallthrough) because the route is absent from the matcher;&lt;/li&gt;
&lt;li&gt;once the matcher is partially rebuilt, data endpoints return HTTP 500 from IRI generation (&lt;code&gt;RouteNotFoundException&lt;/code&gt;), because the URL generator dump is also empty;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/docs.json&lt;/code&gt; returns 0 paths, even though &lt;code&gt;api:debug --list&lt;/code&gt; shows the resources resolving correctly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To recover a frozen container, clear and re-warm the cache (&lt;code&gt;cache:clear&lt;/code&gt;) with the full resource collection present.&lt;/p&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>Mon, 29 Jun 2026 11:21:50 +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>
        
        <item>
            <title>Spryker API roadmap and adoption</title>
            <description>&lt;p&gt;This page continues the &lt;a href=&quot;/docs/integrations/spryker-glue-api/getting-started-with-apis/api-strategy.html&quot;&gt;Spryker API Strategy&lt;/a&gt;. It covers the API types on the roadmap, how to choose one, why API Platform integration, your migration decision, and the rollout timeline.&lt;/p&gt;
&lt;h2 id=&quot;whats-on-the-roadmap&quot;&gt;What’s on the roadmap&lt;/h2&gt;
&lt;p&gt;Planned API types, listed so you can plan ahead. Availability is announced per type; treat these as direction, not a build target.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API type&lt;/th&gt;
&lt;th&gt;Application&lt;/th&gt;
&lt;th&gt;Caller (actor)&lt;/th&gt;
&lt;th&gt;Use it when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Merchant API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Marketplace merchant (single-merchant scope)&lt;/td&gt;
&lt;td&gt;A merchant manages their own catalog, offers, orders, or payouts record-by-record through a UI or tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Merchant Data Exchange API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Merchant-side integration system&lt;/td&gt;
&lt;td&gt;A merchant bulk-imports or bulk-exports their own data between their systems and the marketplace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Exchange API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Platform-operator integration system&lt;/td&gt;
&lt;td&gt;The operator moves large datasets in or out at platform level (PIM, ERP, OMS, BI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Async Event API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Event subscriber (broker or webhook)&lt;/td&gt;
&lt;td&gt;An external system needs to react to Spryker events instead of polling&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;choosing-an-api-type&quot;&gt;Choosing an API type&lt;/h2&gt;
&lt;p&gt;Pick by &lt;strong&gt;who is calling&lt;/strong&gt; and &lt;strong&gt;the shape of the data flow&lt;/strong&gt; (one record at a time, bulk, or event-driven).&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your situation&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A customer browses, shops, and checks out&lt;/td&gt;
&lt;td&gt;Storefront API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An administrator manages catalog, orders, or customers&lt;/td&gt;
&lt;td&gt;Back Office API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A merchant manages their own data through a UI, record by record&lt;/td&gt;
&lt;td&gt;Merchant API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A merchant bulk-imports or bulk-exports their own data&lt;/td&gt;
&lt;td&gt;Merchant Data Exchange API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The platform operator moves large datasets (PIM, ERP, OMS)&lt;/td&gt;
&lt;td&gt;Data Exchange API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An external system reacts to events (order placed, stock changed)&lt;/td&gt;
&lt;td&gt;Async Event API&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Each API type has its own actor and authentication&lt;/strong&gt;, so a token for one type does not grant access to another. Storefront uses customer authentication; Back Office uses administrator authentication; Merchant uses merchant-scoped authentication that restricts every call to a single merchant’s data; the data and event types use system credentials issued by the platform operator.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;why-api-platform-integration&quot;&gt;Why API Platform integration&lt;/h2&gt;
&lt;p&gt;What you get, in developer terms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Contract-first APIs&lt;/strong&gt; → the contract is the source of truth, so you can generate typed clients and catch breaking changes before they ship.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Versioning and content negotiation&lt;/strong&gt; → platform upgrades stop silently breaking your integration; you opt into changes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;application/csv&lt;/code&gt;, &lt;code&gt;application/xml&lt;/code&gt;, &lt;code&gt;application/ld+json&lt;/code&gt;&lt;/strong&gt; → your ERP or PIM integration can drop its custom transformation layer and exchange the format it already speaks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Built for large data exchange&lt;/strong&gt; → bulk import/export is a first-class path, not a workaround.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standardized integration patterns&lt;/strong&gt; → the same conventions across every API type, so a second integration is faster than the first.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runs alongside Glue&lt;/strong&gt; → adopt it per endpoint, with no flag day and nothing to switch off.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;before-and-after-your-migration-decision&quot;&gt;Before and after: your migration decision&lt;/h2&gt;
&lt;p&gt;You only have a decision to make if you’re on Glue and weighing a move. Here’s the whole picture:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;On Glue today&lt;/th&gt;
&lt;th&gt;After moving to API Platform integration&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Existing endpoints&lt;/td&gt;
&lt;td&gt;Keep working, supported, no EOL&lt;/td&gt;
&lt;td&gt;Unchanged — migrate only what you choose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New endpoints you build&lt;/td&gt;
&lt;td&gt;Possible, but no new Spryker features land here&lt;/td&gt;
&lt;td&gt;Recommended path; new features available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Breaking changes on upgrade&lt;/td&gt;
&lt;td&gt;Possible&lt;/td&gt;
&lt;td&gt;Controlled through versioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data format handling&lt;/td&gt;
&lt;td&gt;Custom transformation in your code&lt;/td&gt;
&lt;td&gt;Native CSV / XML / JSON-LD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Effort to adopt&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Incremental, endpoint by endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;You should consider moving when&lt;/strong&gt; you’re building a new integration, exchanging large datasets, connecting an external system (ERP, PIM, OMS), or you want upgrade-safe versioning. &lt;strong&gt;You can stay on Glue&lt;/strong&gt; indefinitely for everything that already works.&lt;/p&gt;
&lt;p&gt;Spryker supports the move with documentation of behavioral differences, endpoint-by-endpoint migration, and conversion tools and reference implementations.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;timeline&quot;&gt;Timeline&lt;/h2&gt;
&lt;p&gt;API Platform integration is generally available. The rollout ran as follows:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Milestone&lt;/th&gt;
&lt;th&gt;When&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Early access&lt;/td&gt;
&lt;td&gt;Through Q1 2026&lt;/td&gt;
&lt;td&gt;Available for new features ahead of GA, with a limited support scope; native features (authentication, codebuckets, full JSON:API) not yet fully integrated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Glue feature freeze&lt;/td&gt;
&lt;td&gt;From Q1 2026&lt;/td&gt;
&lt;td&gt;New Spryker features target API Platform integration only. Glue gets bugfixes, security, and compatibility — no new endpoints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;General availability&lt;/td&gt;
&lt;td&gt;Apr 30, 2026&lt;/td&gt;
&lt;td&gt;Full support; recommended for all new integrations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Glue REST module migration&lt;/td&gt;
&lt;td&gt;In progress&lt;/td&gt;
&lt;td&gt;Spryker-provided modules move to API Platform. Check the &lt;a href=&quot;/docs/dg/dev/architecture/api-platform/migrate-to-api-platform-status.html&quot;&gt;migration status page&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The Q1 feature freeze and the April GA work together: from Q1, new features were built on API Platform integration and shipped through the &lt;strong&gt;early-access&lt;/strong&gt; window, reaching full support at GA. Feature freeze means no new Glue functionality — it is not deprecation, and there is no End-of-Life.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ&lt;/h2&gt;
&lt;p&gt;These cover the edge cases the tables above don’t.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How is the Merchant API different from the Back Office API?&lt;/strong&gt;
Back Office serves Spryker administrators with platform-wide scope. Merchant serves a single marketplace merchant — every call is restricted to that merchant’s own data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How is the Merchant API different from the Merchant Data Exchange API?&lt;/strong&gt;
Both are merchant-scoped. Merchant API is for record-by-record operations through interfaces and tools. Merchant Data Exchange API is for bulk, file-based, or queue-based flows between a merchant’s systems and the marketplace.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How is the Data Exchange API different from the Merchant Data Exchange API?&lt;/strong&gt;
Same capabilities, different scope. Data Exchange operates at platform level with operator credentials. Merchant Data Exchange is restricted to one merchant’s data with per-merchant credentials.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How is the Data Exchange API different from the Async Event API?&lt;/strong&gt;
Data Exchange is request-driven — your system calls Spryker to import, export, or read. Async Event is event-driven — Spryker notifies your system when something happens. Choose by whether you poll or react.&lt;/p&gt;
</description>
            <pubDate>Thu, 25 Jun 2026 13:34:37 +0000</pubDate>
            <link>https://docs.spryker.com/docs/integrations/spryker-glue-api/getting-started-with-apis/api-roadmap-and-adoption.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/integrations/spryker-glue-api/getting-started-with-apis/api-roadmap-and-adoption.html</guid>
            
            
        </item>
        
        <item>
            <title>Spryker API Strategy</title>
            <description>&lt;h2 id=&quot;what-this-means-for-you&quot;&gt;What this means for you&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;On Glue today?&lt;/strong&gt; Nothing breaks. Glue is fully supported, there is no End-of-Life, and you don’t have to migrate existing features.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Building something new?&lt;/strong&gt; Build it on &lt;strong&gt;API Platform integration&lt;/strong&gt;. It is generally available and is where all new Spryker features ship.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Need a Spryker Core feature released after Q1 2026?&lt;/strong&gt; It will be available through API Platform integration only.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Everything below is supporting detail.&lt;/p&gt;
&lt;section class=&apos;info-block info-block--warning&apos;&gt;&lt;i class=&apos;info-block__icon icon-warning&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;
&lt;p&gt;Glue keeps working and isn’t going away. New features are built on API Platform integration. Migration is optional.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;how-the-pieces-fit&quot;&gt;How the pieces fit&lt;/h2&gt;
&lt;p&gt;Spryker’s APIs are described by three &lt;strong&gt;independent&lt;/strong&gt; axes. They are easy to confuse because some names repeat across axes, so read them as orthogonal:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Application&lt;/strong&gt; — &lt;em&gt;where&lt;/em&gt; the API lives. Two of them: &lt;strong&gt;Storefront API&lt;/strong&gt; and &lt;strong&gt;Backend API&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API type&lt;/strong&gt; — &lt;em&gt;what&lt;/em&gt; you call and &lt;em&gt;who&lt;/em&gt; the caller is (a customer, an administrator, a merchant, a system). Several types live under each application.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Foundation&lt;/strong&gt; — &lt;em&gt;how&lt;/em&gt; it’s built underneath: &lt;strong&gt;Glue&lt;/strong&gt; or &lt;strong&gt;API Platform integration&lt;/strong&gt;. This is a foundation beneath the API, not something you call directly.&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API type&lt;/th&gt;
&lt;th&gt;Application (where)&lt;/th&gt;
&lt;th&gt;Foundation (how it’s built)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Storefront API&lt;/td&gt;
&lt;td&gt;Storefront API&lt;/td&gt;
&lt;td&gt;Glue &lt;strong&gt;or&lt;/strong&gt; API Platform integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Back Office API&lt;/td&gt;
&lt;td&gt;Backend API&lt;/td&gt;
&lt;td&gt;Glue &lt;strong&gt;or&lt;/strong&gt; API Platform integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Merchant API&lt;/td&gt;
&lt;td&gt;Backend API&lt;/td&gt;
&lt;td&gt;API Platform integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Merchant Data Exchange API&lt;/td&gt;
&lt;td&gt;Backend API&lt;/td&gt;
&lt;td&gt;API Platform integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data Exchange API&lt;/td&gt;
&lt;td&gt;Backend API&lt;/td&gt;
&lt;td&gt;API Platform integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async Event API&lt;/td&gt;
&lt;td&gt;Backend API&lt;/td&gt;
&lt;td&gt;API Platform integration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Read the table across, not down: each API type belongs to one application and is built on one foundation, and those two facts are independent of each other. “Storefront API” names both an application and the type it hosts; “API Platform integration” is a foundation, not an endpoint you target. During the transition, an existing type can be served by either foundation.&lt;/p&gt;
&lt;section class=&apos;info-block &apos;&gt;&lt;i class=&apos;info-block__icon icon-info&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;&lt;div class=&quot;info-block__title&quot;&gt;Naming watch-out&lt;/div&gt;
&lt;p&gt;The &lt;strong&gt;Backend API&lt;/strong&gt; &lt;em&gt;application&lt;/em&gt; is not the same as the &lt;strong&gt;Back Office API&lt;/strong&gt; &lt;em&gt;type&lt;/em&gt;. The Backend API application hosts several types of API, one of which is Back Office API.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;what-you-can-build-on-today&quot;&gt;What you can build on today&lt;/h2&gt;
&lt;p&gt;These are callable now:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API type&lt;/th&gt;
&lt;th&gt;Application&lt;/th&gt;
&lt;th&gt;Caller (actor)&lt;/th&gt;
&lt;th&gt;Use it when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Storefront API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Storefront&lt;/td&gt;
&lt;td&gt;Customer (authenticated or guest)&lt;/td&gt;
&lt;td&gt;You’re building any customer-facing shopping experience&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Back Office API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Back Office administrator or trusted internal service&lt;/td&gt;
&lt;td&gt;You’re automating or replacing Back Office administration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For the API types on the roadmap, guidance on choosing between them, why API Platform integration, your migration decision, and the rollout timeline, see &lt;a href=&quot;/docs/integrations/spryker-glue-api/getting-started-with-apis/api-roadmap-and-adoption.html&quot;&gt;Spryker API roadmap and adoption&lt;/a&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id=&quot;faq&quot;&gt;FAQ&lt;/h2&gt;
&lt;p&gt;These cover the edge cases the tables above don’t.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is API Platform integration something I call, or something underneath what I call?&lt;/strong&gt;
Underneath. You call an API type (Storefront, Back Office, and so on). API Platform integration is the foundation it’s built on, replacing Glue for new work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How is the Backend API application different from the Back Office API type?&lt;/strong&gt;
The Backend API is the application that hosts several types. Back Office is one of those types, for administrator-level operations. Same word root, different axis.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do I have to migrate my existing Glue APIs?&lt;/strong&gt;
No. Migration is optional and there is no End-of-Life.&lt;/p&gt;
</description>
            <pubDate>Thu, 25 Jun 2026 11:07:26 +0000</pubDate>
            <link>https://docs.spryker.com/docs/integrations/spryker-glue-api/getting-started-with-apis/api-strategy.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/integrations/spryker-glue-api/getting-started-with-apis/api-strategy.html</guid>
            
            
        </item>
        
    </channel>
</rss>
