<?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>Fri, 28 Aug 2026 10:16:15 +0000</lastBuildDate>
        <generator>Jekyll v4.2.2</generator>
        
        
        <item>
            <title>Migrate from OpenSearch 1.3 to 3.5</title>
            <description>This document describes how to migrate an OpenSearch cluster used by a Spryker project from version 1.3 to 3.5.

Between major OpenSearch versions, an index or a cluster setting can become incompatible with the target version, for example because of an outdated Lucene index format, a deprecated setting, or a breaking plugin change. OpenSearch does not support skipping major versions and cannot be upgraded in place while such incompatibilities exist, so the upgrade is blocked until you resolve them. To resolve this, you upgrade the cluster incrementally, from 1.3 to 2.19, and then from 2.19 to 3.5, fixing every incompatibility before each upgrade.

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

Before you start, test the migration in a non-production environment. Blocking writes to an index makes it read-only until the reindexing is complete, so plan the migration for a maintenance window.

{% endinfo_block %}

## 1. Update the required modules

You only need to update the packages for the features used in your project. Check the following table and update the packages that are installed in your project to at least the specified versions:

| Package | Minimum version |
| --- | --- |
| `spryker/sales-return-search` | 1.4.1 |
| `spryker/merchant-search` | 1.3.1 |
| `spryker/product-review` | 2.15.1 |
| `spryker/search-elasticsearch` | 1.23.1 |
| `spryker/service-point-search` | 1.4.1 |
| `spryker-feature/self-service-portal` | 20.9.1 |

Update the packages installed in your project using Composer:

```bash
composer update spryker/merchant-search:&quot;^1.3.1&quot; spryker/product-review:&quot;^2.15.1&quot; spryker/sales-return-search:&quot;^1.4.1&quot; spryker/search-elasticsearch:&quot;^1.23.1&quot; spryker/service-point-search:&quot;^1.4.1&quot; spryker-feature/self-service-portal:&quot;^20.9.1&quot;
```

If your project overrides the search schema of these modules, apply the equivalent changes to your project-level search schema as well.

## 2. Check the upgrade eligibility

Consult the breaking changes and deprecation notices for OpenSearch 2.19, and check your indexes and cluster settings against them to determine whether the cluster is eligible for the upgrade.

- If the cluster is eligible, upgrade it to 2.19 and continue to [4. Check the upgrade eligibility for 3.5](#4-check-the-upgrade-eligibility-for-35).
- If the cluster is not eligible, one or more incompatibilities block the upgrade.
  - For an index with an incompatible index format, unblock the upgrade by reindexing it as described in the following section.
  - For any other incompatibility, for example a deprecated index setting or a breaking plugin change, fix it first. Only continue with the upgrade after all the incompatibilities are resolved.

## 3. Reindex an index to unblock the upgrade

Repeat the following steps for every index with an incompatible index format. Replace `&lt;index&gt;` with the name of the index you are migrating.

### 3.1. Block writes to the index

To prevent data from changing while the index is being cloned, block write operations:

```json
PUT /&lt;index&gt;/_settings
{
  &quot;settings&quot;: {
    &quot;index.blocks.write&quot;: true
  }
}
```

### 3.2. Clone the index

Clone the index into a temporary index:

```json
PUT /&lt;index&gt;/_clone/&lt;index&gt;_tmp
```

### 3.3. Verify the document count

Compare the document count of `&lt;index&gt;` and `&lt;index&gt;_tmp`. The counts must match before you continue.

### 3.4. Delete the original index

```json
DELETE /&lt;index&gt;
```

### 3.5. Re-create the index and its schema

Re-create the index and install its schema using the following console command:

```bash
console search:setup:sources
```

### 3.6. Reindex the documents

Reindex the documents from the temporary index back into the newly created `&lt;index&gt;`:

```json
POST /_reindex?slices=2&amp;wait_for_completion=false
{
  &quot;source&quot;: {
    &quot;index&quot;: &quot;&lt;index&gt;_tmp&quot;
  },
  &quot;dest&quot;: {
    &quot;index&quot;: &quot;&lt;index&gt;&quot;
  }
}
```

### 3.7. Verify the document count again

Compare the document count of `&lt;index&gt;` and `&lt;index&gt;_tmp` again. The counts must match before you continue.

### 3.8. Delete the temporary index

```json
DELETE /&lt;index&gt;_tmp
```

After you reindex all the blocking indexes, upgrade the cluster to version 2.19.

## 4. Check the upgrade eligibility for 3.5

Consult the breaking changes and deprecation notices for OpenSearch 3.5, and check your indexes and cluster settings on version 2.19 against them to determine whether the cluster is eligible for the upgrade.

- If the cluster is eligible, upgrade it to 3.5.
- If the cluster is not eligible, incompatibilities again block the upgrade. Resolve them the same way as in step 2: reindex every index with an incompatible index format by repeating the steps in [3. Reindex an index to unblock the upgrade](#3-reindex-an-index-to-unblock-the-upgrade), and fix any other incompatibility. Then upgrade the cluster to 3.5.
</description>
            <pubDate>Wed, 26 Aug 2026 12:12:26 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/search/latest/base-shop/install-and-upgrade/migrate-from-opensearch-1.3-to-3.5.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/search/latest/base-shop/install-and-upgrade/migrate-from-opensearch-1.3-to-3.5.html</guid>
            
            
        </item>
        
        <item>
            <title>Upgrade the PurchasingControl module</title>
            <description>{% include pbc/all/upgrade-modules/upgrade-the-purchasingcontrol-module.md %} &lt;!-- To edit, see /_includes/pbc/all/upgrade-modules/upgrade-the-purchasingcontrol-module.md --&gt;
</description>
            <pubDate>Tue, 25 Aug 2026 08:40:54 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/cart-and-checkout/latest/base-shop/install-and-upgrade/upgrade-modules/upgrade-the-purchasingcontrol-module.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/cart-and-checkout/latest/base-shop/install-and-upgrade/upgrade-modules/upgrade-the-purchasingcontrol-module.html</guid>
            
            
        </item>
        
        <item>
            <title>Purchasing Control feature overview</title>
            <description>The *Purchasing Control* feature lets B2B companies track and control procurement spending by assigning orders to cost centers and enforcing configurable budget rules. It extends the existing [Approval Process](/docs/pbc/all/cart-and-checkout/latest/base-shop/feature-overviews/approval-process-feature-overview.html) with a second dimension of spending governance: per-department or per-project budget limits that work alongside the existing per-person permission limits.

Buyers assign a cost center and a budget to a cart at checkout or to a quote request, and agents can do the same on a customer&apos;s behalf.

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

This feature is available in the Back Office and on the Storefront.

{% endinfo_block %}

## Cost centers

A *cost center* is an organizational unit within a company that incurs costs but does not directly generate revenue. Companies use cost centers to track and control spending by department, project, location, or function.

**Common examples:**

- **Departmental:** Marketing, Engineering, HR, Facilities
- **Project-based:** Office Renovation Q2 2026, Trade Show Berlin
- **Location-based:** Warehouse Berlin, Office London

Every purchase a buyer makes is charged to a cost center so the company can track where money is being spent. In ERP systems such as SAP, Oracle, and Microsoft Dynamics, cost centers are a foundational accounting concept - orders flow into the ERP tagged with a cost center code, enabling financial reporting and cost allocation.

### Deactivated cost centers and budgets

A cost center or budget can be deactivated instead of deleted, which preserves the history of the orders already charged to it. Deactivated records are not offered in the cost center and budget selectors, and a deactivated cost center is no longer shown where the assignment of a cart or quote is displayed. If every cost center available to a buyer is deactivated, or if the selected cost center has no active budget left, no choices are rendered and the buyer cannot assign one until an active record is available again.

## Budgets

A *budget* is a spending limit assigned to a cost center for a defined period - monthly, quarterly, or annually. It represents the maximum amount that a department or project is authorized to spend in that period.

**Example:** The Marketing department has a quarterly procurement budget of €50,000 for office supplies and event materials. Once that budget is consumed, further purchases are either blocked, flagged for review, or escalated for approval.

### Budget enforcement rules

Each budget is configured with one of three enforcement rules:

| RULE | DESCRIPTION |
| --- | --- |
| Block | The order is rejected outright when the budget is exceeded. The buyer cannot proceed to checkout. |
| Warn | A warning is displayed to the buyer, but they can proceed. |
| Require Approval | The order is sent for approval when the budget is exceeded. The buyer cannot complete checkout until an approver accepts the order. |

### Budgets in recurring orders

With the [Recurring Orders feature](/docs/pbc/all/order-experience-management/latest/base-shop/feature-overviews/recurring-orders-feature-overview.html) installed, a recurring order carries a cost center and a budget, selected on the recurring order forms and changeable when the buyer edits the schedule or approves a review.

Budgets with the **Require Approval** enforcement rule cannot be used for a recurring order. A recurring order places its follow-up orders unattended, so no approver can accept them at placement time. The restriction applies in two places:

- Such budgets are not offered in the recurring order budget selector.
- Checkout is blocked if a quote being set up as a recurring order carries one. This check applies regardless of the quote grand total and of the remaining budget amount, so the budget does not have to be exceeded for the block to take effect.

Regular, non-recurring checkouts are not affected—there, the **Require Approval** rule behaves as described above.

## Relationship to the Approval Process

Spryker&apos;s existing Approval Process triggers a workflow when a buyer&apos;s order exceeds their *Buy up to grand total* permission. The Purchasing Control feature adds a parallel check: an order might be within a buyer&apos;s personal permission limit but still exceed the cost center&apos;s remaining budget.

Both checks run independently at checkout. If either the permission limit or the budget rule is triggered, the configured action - block, warn, or require approval - is applied. This gives companies layered spending governance: per-person limits *and* per-department or per-project limits.

{% info_block infoBox &quot;Permissions required for the Require Approval enforcement rule&quot; %}

To use budgets with the **Require Approval** enforcement rule, the following [Approval Process](/docs/pbc/all/cart-and-checkout/latest/base-shop/feature-overviews/approval-process-feature-overview.html) permissions must be assigned to the relevant company roles:

| PERMISSION | REQUIRES |
| --- | --- |
| Buy up to grand total | Send cart for approval |
| Send cart for approval | Buy up to grand total |
| Approve up to grand total | None |

{% endinfo_block %}

{% info_block warningBox &quot;Approvals within a business unit&quot; %}

Approvers can only approve orders of employees within their own business unit. This constraint applies to both permission-based and budget-based approval requests.

{% endinfo_block %}

## Cost centers and budgets in the procurement workflow

The typical B2B procurement flow involving cost centers and budgets:

1. **Finance sets budgets.** At the start of a fiscal period, finance allocates budgets to each cost center.
2. **Buyers are assigned to cost centers.** Buyers are linked to one or more cost centers they are authorized to purchase against. Cost centers are linked to company business units, so all users in a business unit are automatically assigned to the corresponding cost centers.
3. **Orders are tagged.** At checkout, the buyer selects the cost center the purchase is charged to and the budget it is drawn from. Both fields are saved together in a single step:
   - The budget dropdown offers the budgets of every cost center available to the buyer, narrowed to the selected cost center.
   - Changing the cost center narrows the budget list immediately, without reloading the page. If the previously selected budget does not belong to the new cost center, the budget field is cleared.
   - If the selected cost center has no active budgets, the budget field is hidden and a message states that no budgets are available.
   - A budget that is selected must belong to the selected cost center. The pairing is validated on the server, so a mismatched combination is rejected even when the browser-side filtering is bypassed.
   - The selector form does not enforce a budget selection by itself, but the field is marked as required in the browser whenever the selected cost center has active budgets. An order cannot be placed without an active budget: if the buyer&apos;s business unit has active cost centers and no active budget is resolved, checkout fails and the buyer is asked to select a cost center and a budget.
4. **Budget is validated.** The system checks whether the order total fits within the remaining budget for the selected cost center.
5. **Enforcement rules apply.** Based on the configured rule, the order is blocked, a warning is shown, or approval is required.
6. **Budget is consumed.** Once the order is confirmed, the budget balance is reduced by the order amount.
7. **Budget is restored.** If the order is cancelled or refunded, the budget balance is restored by the amount corresponding to the cancelled or refunded items. For partial cancellations or refunds, only the amounts of the affected items are restored.

## Checkout validation outcomes

| SCENARIO | OUTCOME |
| --- | --- |
| Within budget and within permission limit | Buyer completes checkout without additional steps. |
| Exceeds budget  -  Warn rule | A warning is displayed; the buyer can proceed to checkout. |
| Exceeds budget  -  Require Approval rule | The order is sent for approval; the buyer cannot complete checkout until approved. |
| Exceeds Buy up to grand total permission limit | The order is sent for approval, same as the standard Approval Process. |
| Exceeds budget  -  Block rule | Checkout is blocked; no approval option is available. |
| Recurring order with a Require Approval budget | Checkout is blocked, whether or not the budget is exceeded. The buyer must select a budget bound to the Block or Warn rule. |
| No active budget resolved while the business unit has active cost centers | Checkout fails; the buyer must select a cost center and a budget before placing the order. |

## Quote lock

When an order is sent for approval - whether triggered by a budget rule or a permission limit - the quote is locked to preserve the order state during the approval review. Neither the buyer nor the approver can modify the quote while it is pending approval. For details, see [Quote lock functionality](/docs/pbc/all/cart-and-checkout/latest/base-shop/feature-overviews/approval-process-feature-overview.html#quote-lock-functionality).

## Cost centers and budgets on quote requests

Buyers do not have to wait until checkout to attribute spending. The same cost center and budget selection is available on quote requests, so a purchase is assigned to a cost center while it is still being negotiated.

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

Cost center selection on quote requests requires the [Quotation Process](/docs/pbc/all/request-for-quote/latest/install-and-upgrade/install-features/install-the-quotation-process-feature.html) feature.

{% endinfo_block %}

| CONTEXT | WHERE THE SELECTION IS AVAILABLE |
| --- | --- |
| Buyer (Storefront) | Quote request details page and quote request edit page in the company area. |
| Agent (Storefront) | Agent quote request details page and agent quote request edit page. |

### Cost centers follow the quote request owner

Cost centers are scoped per company business unit. On a quote request, the applicable cost centers are those of the business unit that owns the request - the business unit of the company user who created it - and not those of the person currently viewing it. An agent working on a customer&apos;s quote request therefore sees the customer&apos;s cost centers rather than their own.

Buyers can only change the cost center on their own quote requests. A quote request reference that belongs to another company user is treated as not found, so an unauthorized reference is indistinguishable from a reference that does not exist.

### Editability

On a quote request, selecting a budget is optional: there is no checkout to pass, so a quote request is saved with a cost center and no budget. A budget that is selected must still belong to the selected cost center.

A cost center and a budget can be changed only while the quote request is in an editable status. The status check runs on the server, so a quote request that is no longer editable is rejected even when the form is submitted directly. Quote requests that are not editable still display the assigned cost center and budget as read-only.

## Roles and capabilities

| ROLE | CAPABILITIES |
| --- | --- |
| Site Operator (Back Office) | Create, update, activate, and deactivate cost centers. Assign cost centers to business units. Create and manage budgets with amount, period, currency, and enforcement rule. View the **Cost Center** column in the orders table. Filter and search orders by cost center and budget. View spend-vs-budget reports. Export reports to CSV. Review the audit log. Import cost centers, budgets, and business unit assignments in bulk using data import. |
| Cost Center Manager (Storefront) | Create, update, activate, and deactivate cost centers and budgets from the company area. Requires the **Manage Cost Centers** permission assigned to their company role. |
| Buyer (Storefront) | Select a cost center and budget at checkout. Assign a cost center and budget to their own quote requests. View the remaining budget for the selected cost center. Submit orders for approval when required. Filter order history by cost center and budget. View the assigned cost center and budget on order detail pages. |
| Approver (Storefront) | Review locked quotes pending approval. Approve or reject orders, including those triggered by budget rules. |
| Agent (Storefront) | Assign a cost center and budget to a customer&apos;s quote request on the customer&apos;s behalf, using the cost centers of the quote request owner&apos;s business unit. |

## Related Developer documents

| INSTALLATION GUIDES |
| --- |
| [Install the Purchasing Control feature](/docs/pbc/all/cart-and-checkout/latest/base-shop/install-and-upgrade/install-features/install-the-purchasing-control-feature.html) |

| MIGRATION GUIDES |
| --- |
| [Upgrade the PurchasingControl module](/docs/pbc/all/cart-and-checkout/latest/base-shop/install-and-upgrade/upgrade-modules/upgrade-the-purchasingcontrol-module.html) |
</description>
            <pubDate>Tue, 25 Aug 2026 08:40:54 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/cart-and-checkout/latest/base-shop/feature-overviews/purchasing-control-feature-overview.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/cart-and-checkout/latest/base-shop/feature-overviews/purchasing-control-feature-overview.html</guid>
            
            
        </item>
        
        <item>
            <title>Install the Purchasing Control feature</title>
            <description>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) |
| Quotation Process | {{page.release_tag}} | [Install the Quotation Process feature](/docs/pbc/all/request-for-quote/latest/install-and-upgrade/install-features/install-the-quotation-process-feature.html) |

{% info_block infoBox &quot;Quotation Process&quot; %}

The Quotation Process feature is required only if you want buyers and agents to assign cost centers and budgets to quote requests. The `spryker/quote-request` and `spryker/quote-request-agent` modules are installed as dependencies of `spryker-feature/purchasing-control` in either case, but the Storefront quote request pages are only available with the Quotation Process feature installed. If you do not need this part of the feature, skip [Integrate cost centers with quote requests](#integrate-cost-centers-with-quote-requests).

{% endinfo_block %}

### 1) Install the required modules

```bash
composer require spryker-feature/purchasing-control:&quot;^2.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 %}

#### Set up Recurring Orders plugins

{% info_block infoBox &quot;Recurring Orders feature&quot; %}

This step is only required if your project uses the [Recurring Orders feature](/docs/pbc/all/order-experience-management/latest/base-shop/feature-overviews/recurring-orders-feature-overview.html). The plugins belong to the Purchasing Control module but register in the `OrderExperienceManagement` dependency providers.

{% endinfo_block %}

| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
| --- | --- | --- | --- |
| BudgetApprovalRuleRecurringOrderCheckoutValidatorPlugin | Blocks checkout when a quote being set up as a recurring order carries a budget with the `require_approval` enforcement rule. Applies regardless of the quote grand total and the remaining budget amount. Passes when no budget is selected or the budget uses another enforcement rule. | Recurring Orders feature | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\OrderExperienceManagement |
| CostCenterRecurringOrderApproveFormExpanderPlugin | Adds cost center and budget dropdowns to the recurring order review approve form and validates the selected pair server-side. | Recurring Orders feature | SprykerFeature\Yves\PurchasingControl\Plugin\OrderExperienceManagement |
| CostCenterRecurringScheduleEditFormExpanderPlugin | Adds cost center and budget dropdowns to the recurring schedule edit form and validates the selected pair server-side. | Recurring Orders feature | SprykerFeature\Yves\PurchasingControl\Plugin\OrderExperienceManagement |

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

```php
&lt;?php

namespace Pyz\Zed\OrderExperienceManagement;

use SprykerFeature\Zed\OrderExperienceManagement\OrderExperienceManagementDependencyProvider as SprykerOrderExperienceManagementDependencyProvider;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\OrderExperienceManagement\BudgetApprovalRuleRecurringOrderCheckoutValidatorPlugin;

class OrderExperienceManagementDependencyProvider extends SprykerOrderExperienceManagementDependencyProvider
{
    /**
     * @return array&lt;\SprykerFeature\Zed\OrderExperienceManagement\Dependency\Plugin\RecurringOrderCheckoutValidatorPluginInterface&gt;
     */
    protected function getRecurringOrderCheckoutValidatorPlugins(): array
    {
        return [
            new BudgetApprovalRuleRecurringOrderCheckoutValidatorPlugin(), #PurchasingControlFeature
        ];
    }
}
```

For the two Yves form expander plugins and the budget enforcement rules that can be selected on the recurring order forms, see [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).

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

Set a budget&apos;s enforcement rule to **Require approval**, assign it to a cart, and set up that cart as a recurring order at checkout. Make sure checkout is blocked with a message stating that the selected budget requires approval and cannot be used for a recurring order.

{% 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
purchasing_control.budget.validation.cost_center_mismatch,&quot;The selected budget does not belong to the selected cost center.&quot;,en_US
purchasing_control.budget.validation.cost_center_mismatch,&quot;Das ausgewählte Budget gehört nicht zur ausgewählten Kostenstelle.&quot;,de_DE
purchasing_control.quote_request.cost_center_updated,Cost center and budget have been saved.,en_US
purchasing_control.quote_request.cost_center_updated,Kostenstelle und Budget wurden gespeichert.,de_DE
```

If your project also uses the Recurring Orders feature, import the following keys as well. They translate the cost center and budget dropdowns on the recurring order approve and schedule edit forms, and the budget summary rendered by the `recurring-order-budget-summary` molecule:

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

```csv
purchasing_control.recurring_order.budget.total,Total budget,en_US
purchasing_control.recurring_order.budget.total,Gesamtbudget,de_DE
purchasing_control.recurring_order.budget.used,Used,en_US
purchasing_control.recurring_order.budget.used,Verbraucht,de_DE
purchasing_control.recurring_order.budget.remaining,Remaining,en_US
purchasing_control.recurring_order.budget.remaining,Verbleibend,de_DE
purchasing_control.recurring_order.budget.usage,%used% used of %total%,en_US
purchasing_control.recurring_order.budget.usage,%used% von %total% verwendet,de_DE
purchasing_control.recurring_order.cost_center_required,Select cost center,en_US
purchasing_control.recurring_order.cost_center_required,Kostenstelle wählen,de_DE
purchasing_control.recurring_order.budget_required,Select budget,en_US
purchasing_control.recurring_order.budget_required,Budget wählen,de_DE
purchasing_control.recurring_order.budget_cost_center_mismatch,The selected budget does not belong to the selected cost center.,en_US
purchasing_control.recurring_order.budget_cost_center_mismatch,Das ausgewählte Budget gehört nicht zur ausgewählten Kostenstelle.,de_DE
purchasing_control.validation.inactive-budget,The selected budget is no longer available. Please select another budget or contact your manager.,en_US
purchasing_control.validation.inactive-budget,Das ausgewählte Budget ist nicht mehr verfügbar. Bitte wählen Sie ein anderes Budget oder kontaktieren Sie Ihren Manager.,de_DE
purchasing_control.validation.approval-rule-not-supported,&quot;The selected budget requires approval and cannot be used for a recurring order. Please select another budget.&quot;,en_US
purchasing_control.validation.approval-rule-not-supported,&quot;Das ausgewählte Budget erfordert eine Genehmigung und kann nicht für eine wiederkehrende Bestellung verwendet werden. Bitte wählen Sie ein anderes Budget aus.&quot;,de_DE
```

{% info_block infoBox &quot;Recurring order translations&quot; %}

These keys render only on recurring order screens. Register the plugins that display them as described in [Set up Recurring Orders plugins](#set-up-recurring-orders-plugins) and [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).

{% endinfo_block %}

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 cost center and budget assigned to a quote, taking a `QuoteTransfer` as input and an optional flag that adds a budget usage summary. Renders only where a template calls it—see the note below. Only active cost centers are resolved, so a deactivated cost center makes the widget render nothing. | 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 infoBox &quot;Where CostCenterDetailWidget is rendered&quot; %}

Unlike the other widgets in this table, no Purchasing Control template calls `CostCenterDetailWidget`. It renders only where another template calls it explicitly. In the demo shop, its single caller is the recurring order detail sidebar of the [Recurring Orders feature](/docs/pbc/all/order-experience-management/latest/base-shop/feature-overviews/recurring-orders-feature-overview.html). To display the assigned cost center and budget elsewhere—on the cart page or a quote detail page, for example—call the widget from that template:

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

Pass a second argument to add a budget usage summary—the total, used, and remaining amounts, plus a used-percentage bar—below the cost center and budget names:

```twig
{% raw %}{% widget &apos;CostCenterDetailWidget&apos; args [data.quote, true] only %}{% endwidget %}{% endraw %}
```

The summary is rendered by the `recurring-order-budget-summary` molecule the module ships. It is omitted when the argument is `false` or not passed, and also when the quote carries no budget or no currency.

{% endinfo_block %}

{% 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.
- Call `CostCenterDetailWidget` from a template with a quote that has a cost center assigned, and make sure the cost center and budget names are displayed. Deactivate that cost center and make sure the names are no longer 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. Select a cost center and make sure the budget dropdown offers only the budgets of that cost center, and that budgets of other cost centers are both hidden and disabled.

{% 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. Also adds the POST-only routes that update the cost center and budget of a quote request in the customer and agent contexts. | 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 %}

## Integrate cost centers with quote requests

Follow the steps below to let buyers and agents assign a cost center and a budget to a quote request. Skip this section if you do not use quote requests.

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

This part of the feature requires the [Quotation Process](/docs/pbc/all/request-for-quote/latest/install-and-upgrade/install-features/install-the-quotation-process-feature.html) feature.

{% endinfo_block %}

### 1) Install the required modules

```bash
composer require spryker-shop/quote-request-page:&quot;^3.8.0&quot; spryker-shop/quote-request-agent-page:&quot;^3.7.0&quot; --update-with-dependencies
```

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

Make sure the following modules are installed at the specified versions or higher:

| MODULE | EXPECTED VERSION |
| --- | --- |
| QuoteRequestPage | 3.8.0 |
| QuoteRequestAgentPage | 3.7.0 |

{% endinfo_block %}

### 2) Allow the cost center and budget quote fields for saving

Add `ID_COST_CENTER` and `ID_BUDGET` to the quote fields that are persisted with a quote request version. Without this configuration, both fields are stripped when the quote request version is saved: the update appears to succeed, but the selection is lost.

**src/Pyz/Zed/QuoteRequest/QuoteRequestConfig.php**

```php
&lt;?php

namespace Pyz\Zed\QuoteRequest;

use Generated\Shared\Transfer\QuoteTransfer;
use Spryker\Zed\QuoteRequest\QuoteRequestConfig as SprykerQuoteRequestConfig;

class QuoteRequestConfig extends SprykerQuoteRequestConfig
{
    /**
     * @return array&lt;string&gt;
     */
    public function getQuoteFieldsAllowedForSaving(): array
    {
        return array_merge(parent::getQuoteFieldsAllowedForSaving(), [
            // ...
            QuoteTransfer::ID_COST_CENTER, #PurchasingControlFeature
            QuoteTransfer::ID_BUDGET, #PurchasingControlFeature
        ]);
    }
}
```

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

Assign a cost center and a budget to a quote request, then reload the quote request details page. Make sure the selection is still displayed. In the database, make sure `spy_quote_request_version.quote` contains the `idCostCenter` and `idBudget` values.

{% endinfo_block %}

### 3) Set up widgets

Register the following global widgets:

| WIDGET | DESCRIPTION | NAMESPACE |
| --- | --- | --- |
| QuoteRequestCostCenterSelectorWidget | Renders the cost center and budget selection UI on the quote request details and quote request edit pages. Takes a `QuoteRequestTransfer` and an optional form action route name. When the route name is omitted, the form posts to `company/cost-center/update-quote-request`. | SprykerFeature\Yves\PurchasingControl\Widget |
| QuoteRequestAgentCostCenterSelectorWidget | Renders the same selection UI on the agent quote request details and agent quote request edit pages. Takes the same two arguments. When the route name is omitted, the form posts to `agent/quote-request/cost-center/update`. | SprykerFeature\Yves\PurchasingControl\Widget |

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

```php
&lt;?php

namespace Pyz\Yves\ShopApplication;

use SprykerFeature\Yves\PurchasingControl\Widget\QuoteRequestAgentCostCenterSelectorWidget;
use SprykerFeature\Yves\PurchasingControl\Widget\QuoteRequestCostCenterSelectorWidget;
use SprykerShop\Yves\ShopApplication\ShopApplicationDependencyProvider as SprykerShopApplicationDependencyProvider;

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

Both widgets are rendered from a `costCenter` block in the `quote-request-details.twig` and `quote-request-edit.twig` templates of the `QuoteRequestPage` and `QuoteRequestAgentPage` modules. In a project that uses these templates as they are shipped, no template changes are required. To change or remove the placement, override the `costCenter` block.

{% info_block warningBox &quot;Template overrides&quot; %}

If your project overrides any of the four templates, the widget is not rendered on the affected pages, because your override replaces the module template that contains the `costCenter` block. Re-base your overrides against the new module templates, or add the `costCenter` block to them.

{% endinfo_block %}

The second argument controls where the form returns to after saving. The details pages omit it, so the widget falls back to the details route. The edit pages pass it explicitly: the `QuoteRequestPage` edit template passes `company/cost-center/update-quote-request-from-edit`, and the `QuoteRequestAgentPage` edit template passes `agent/quote-request/cost-center/update-from-edit`. Pass a route name of your own only if you also register a route that returns to your page.

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

The widgets suppress themselves when there is nothing to show. An editable quote request renders the selection form only if at least one cost center is available to the owner&apos;s business unit. A quote request that is not editable displays the assigned cost center and budget as read-only.

{% endinfo_block %}

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

- On the Storefront, open a quote request in an editable status and make sure the cost center and budget selection is displayed on both the details page and the edit page.
- Select a cost center and a budget, then submit the form. Make sure a success message is displayed and the page returns to the details page or the edit page, depending on where you started.
- Open a quote request that is not editable and make sure the assigned cost center and budget are displayed as read-only, without a submit button.
- As an agent, open a customer&apos;s quote request and make sure the cost center dropdown lists the cost centers of the customer&apos;s business unit, not your own.

{% endinfo_block %}

### 4) Review the quote request routes

The `CostCenterRouteProviderPlugin` that you registered in [Set up routes](#5-set-up-routes) adds the following routes. All of them accept `POST` requests only, and each one determines its own return page, so the redirect target cannot be supplied by the request.

| ROUTE NAME | PATH | RETURNS TO |
| --- | --- | --- |
| `company/cost-center/update-quote-request` | `/company/cost-center/update-quote-request/{quoteRequestReference}` | Quote request details page |
| `company/cost-center/update-quote-request-from-edit` | `/company/cost-center/update-quote-request-from-edit/{quoteRequestReference}` | Quote request edit page |
| `agent/quote-request/cost-center/update` | `/agent/quote-request/cost-center/update/{quoteRequestReference}` | Agent quote request details page |
| `agent/quote-request/cost-center/update-from-edit` | `/agent/quote-request/cost-center/update-from-edit/{quoteRequestReference}` | Agent quote request edit page |

The two `-from-edit` routes are reached only when a widget receives a form action route name as its second argument. For details, see [Set up widgets](#3-set-up-widgets).

The two agent routes are deliberately placed under `/agent/` so that they are covered by the agent firewall.

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

- Make sure a `GET` request to any of the four paths is rejected.
- Make sure a buyer cannot update the cost center of a quote request that belongs to another company user: the request must return `404`, the same response as for a quote request reference that does not exist.
- Make sure an anonymous request to either agent path is rejected by the agent firewall.

{% endinfo_block %}
</description>
            <pubDate>Tue, 25 Aug 2026 08:40:54 +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>Install the Configuration Management feature</title>
            <description>This document describes how to install the Configuration Management feature.

## Prerequisites

Update the required modules:

| NAME | VERSION |
|----|---------|
| Kernel | ^3.82   |
| Store | ^1.36   |
| Translator   | ^1.15   |

## Install feature core

### 1) Install the required modules

Install the required modules using Composer:

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

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

Make sure the following modules have been installed:

| MODULE | EXPECTED DIRECTORY |
| --- | --- |
| Configuration | vendor/spryker/configuration |

{% endinfo_block %}

### 2) Set up configuration

Add the following configuration to extend scope support with store-level configuration:

| CONFIGURATION | SPECIFICATION | NAMESPACE |
| --- | --- | --- |
| ConfigurationConfig | Adds `store` scope to available scopes and defines scope hierarchy (store inherits from global). | Pyz\Shared\Configuration |

**src/Pyz/Shared/Configuration/ConfigurationConfig.php**

```php
&lt;?php

/**
 * This file is part of the Spryker Suite.
 * For full license information, please view the LICENSE file that was distributed with this source code.
 */

declare(strict_types = 1);

namespace Pyz\Shared\Configuration;

use Spryker\Shared\Configuration\ConfigurationConfig as SprykerConfigurationConfig;
use Spryker\Shared\Configuration\ConfigurationConstants;

class ConfigurationConfig extends SprykerConfigurationConfig
{
    /**
     * @uses \Spryker\Shared\Store\StoreConstants::SCOPE_STORE
     */
    public const string SCOPE_STORE = &apos;store&apos;;

    public function getAvailableScopes(): array
    {
        $availableScopes = parent::getAvailableScopes();
        $availableScopes[] = static::SCOPE_STORE;

        return $availableScopes;
    }

    public function getScopeHierarchy(): array
    {
        $scopeHierarchy = parent::getScopeHierarchy();
        $scopeHierarchy[static::SCOPE_STORE] = ConfigurationConstants::SCOPE_GLOBAL;

        return $scopeHierarchy;
    }
}
```

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

Make sure that after reading a configuration value with `store` scope, the system falls back to the `global` scope value if no store-specific value is set.

{% endinfo_block %}

#### 2.1) Set up encryption for secret settings

Add encryption key configuration for encrypting and decrypting secret configuration values:

**config/Shared/config_default.php**

Add the import statement:

```php
use Spryker\Shared\Configuration\ConfigurationConstants;
```

Add the encryption configuration:

```php
// Configuration system
$config[ConfigurationConstants::ENCRYPTION_KEY] = hex2bin(getenv(&apos;SPRYKER_CONFIGURATION_ENCRYPTION_KEY&apos;) ?: &apos;&apos;) ?: null;
$config[ConfigurationConstants::ENCRYPTION_INIT_VECTOR] = hex2bin(getenv(&apos;SPRYKER_CONFIGURATION_ENCRYPTION_INIT_VECTOR&apos;) ?: &apos;&apos;) ?: null;
```

#### 2.2) Provide environment variables


For local development, add the following environment variables to your deploy file (`deploy.dev.yml` or equivalent):

```yaml
image:
    environment:
        SPRYKER_CONFIGURATION_ENCRYPTION_KEY: &apos;&lt;your-64-char-hex-key&gt;&apos;
        SPRYKER_CONFIGURATION_ENCRYPTION_INIT_VECTOR: &apos;&lt;your-32-char-hex-iv&gt;&apos;
```

In Cloud, add environment variables using [Parameter Store](/docs/ca/dev/add-variables-in-the-parameter-store).

To generate new keys, run:

```bash
openssl rand -hex 32  # generates SPRYKER_CONFIGURATION_ENCRYPTION_KEY
openssl rand -hex 16  # generates SPRYKER_CONFIGURATION_ENCRYPTION_INIT_VECTOR
```

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

1. Create a setting with `secret: true` in a YAML schema and run `configuration:sync`.
2. Set a value for the secret setting in the Back Office.
3. Check the `spy_configuration_value` table — the stored value must be encrypted (not plain text).
4. Read the value back in the Back Office — it must be decrypted and displayed correctly.

{% endinfo_block %}

### 3) Set up the database schema and transfer objects

#### 3.1) Set up database schema extension

Add event behavior to the `spy_configuration_value` table to enable Publish &amp; Synchronize:

**src/Pyz/Zed/Configuration/Persistence/Propel/Schema/spy_configuration.schema.xml**

```xml
&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;database xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; name=&quot;zed&quot; xsi:noNamespaceSchemaLocation=&quot;http://static.spryker.com/schema-01.xsd&quot; namespace=&quot;Orm\Zed\Configuration\Persistence&quot; package=&quot;src.Orm.Zed.Configuration.Persistence&quot;&gt;

    &lt;table name=&quot;spy_configuration_value&quot; phpName=&quot;SpyConfigurationValue&quot;&gt;
        &lt;behavior name=&quot;event&quot;&gt;
            &lt;parameter name=&quot;spy_configuration_value_all&quot; column=&quot;*&quot; keep-additional=&quot;true&quot;/&gt;
        &lt;/behavior&gt;
    &lt;/table&gt;

&lt;/database&gt;
```

#### 3.2) Apply database changes and generate transfers

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

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

Make sure that the following changes have been applied by checking your database:

| DATABASE ENTITY | TYPE | EVENT |
| --- | --- | --- |
| spy_configuration_value | table | created |
| spy_configuration_storage | table | created |

{% endinfo_block %}

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

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

| TRANSFER | TYPE | EVENT | PATH |
| --- | --- | --- | --- |
| ConfigurationSetting | class | created | src/Generated/Shared/Transfer/ConfigurationSettingTransfer |
| ConfigurationValue | class | created | src/Generated/Shared/Transfer/ConfigurationValueTransfer |
| ConfigurationScope | class | created | src/Generated/Shared/Transfer/ConfigurationScopeTransfer |
| ConfigurationFeature | class | created | src/Generated/Shared/Transfer/ConfigurationFeatureTransfer |
| ConfigurationTab | class | created | src/Generated/Shared/Transfer/ConfigurationTabTransfer |
| ConfigurationGroup | class | created | src/Generated/Shared/Transfer/ConfigurationGroupTransfer |
| ConfigurationConstraint | class | created | src/Generated/Shared/Transfer/ConfigurationConstraintTransfer |
| ConfigurationDependency | class | created | src/Generated/Shared/Transfer/ConfigurationDependencyTransfer |
| ConfigurationSyncResponse | class | created | src/Generated/Shared/Transfer/ConfigurationSyncResponseTransfer |
| ConfigurationSettingCollection | class | created | src/Generated/Shared/Transfer/ConfigurationSettingCollectionTransfer |
| ConfigurationScopeCollection | class | created | src/Generated/Shared/Transfer/ConfigurationScopeCollectionTransfer |
| ConfigurationValueRequest | class | created | src/Generated/Shared/Transfer/ConfigurationValueRequestTransfer |
| ConfigurationValueResponse | class | created | src/Generated/Shared/Transfer/ConfigurationValueResponseTransfer |
| ConfigurationValidationRequest | class | created | src/Generated/Shared/Transfer/ConfigurationValidationRequestTransfer |
| ConfigurationValidationResponse | class | created | src/Generated/Shared/Transfer/ConfigurationValidationResponseTransfer |
| ConfigurationError | class | created | src/Generated/Shared/Transfer/ConfigurationErrorTransfer |
| ConfigurationValueCollectionRequest | class | created | src/Generated/Shared/Transfer/ConfigurationValueCollectionRequestTransfer |
| ConfigurationValueDeletion | class | created | src/Generated/Shared/Transfer/ConfigurationValueDeletionTransfer |
| ConfigurationValueCollectionResponse | class | created | src/Generated/Shared/Transfer/ConfigurationValueCollectionResponseTransfer |
| ConfigurationSettingValuesCriteria | class | created | src/Generated/Shared/Transfer/ConfigurationSettingValuesCriteriaTransfer |
| ConfigurationSettingValueCollection | class | created | src/Generated/Shared/Transfer/ConfigurationSettingValueCollectionTransfer |
| ConfigurationStorage | class | created | src/Generated/Shared/Transfer/ConfigurationStorageTransfer |
| ConfigurationFileUpload | class | created | src/Generated/Shared/Transfer/ConfigurationFileUploadTransfer |
| ConfigurationFileUploadCollectionRequest | class | created | src/Generated/Shared/Transfer/ConfigurationFileUploadCollectionRequestTransfer |
| ConfigurationFileUploadCollectionResponse | class | created | src/Generated/Shared/Transfer/ConfigurationFileUploadCollectionResponseTransfer |

{% endinfo_block %}

### 4) Add translations

Regenerate the Zed translator cache to pick up the Configuration Management Back Office UI translations:

```bash
console translator:generate-cache
```

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

1. Navigate to the Configuration Management page in the Back Office.
2. Verify that all UI labels, buttons, and messages are displayed in the correct locale.
3. Switch to German locale and verify the German translations appear.

{% endinfo_block %}

### 5) Configure navigation

Add the Configuration Management entry to `config/Zed/navigation.xml`:

```xml
&lt;configuration-management&gt;
    &lt;label&gt;Configuration&lt;/label&gt;
    &lt;title&gt;Configuration Management&lt;/title&gt;
    &lt;icon&gt;settings&lt;/icon&gt;
    &lt;bundle&gt;configuration&lt;/bundle&gt;
    &lt;controller&gt;manage&lt;/controller&gt;
    &lt;action&gt;index&lt;/action&gt;
    &lt;visible&gt;1&lt;/visible&gt;
&lt;/configuration-management&gt;
```

Execute the following command to clear the navigation cache:

```bash
console navigation:cache:remove
```

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

Log in to the Back Office and verify that the **Configuration** menu item appears in the main navigation sidebar.

{% endinfo_block %}

### 6) Set up behavior

#### 6.1) Register console commands and application plugin

Register the configuration sync console command and the application plugin that exposes the Configuration facade as an application service for direct Zed access from the Client layer:

| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
| --- | --- | --- | --- |
| ConfigurationSyncConsole | Synchronizes configuration schemas from YAML files and generates the merged schema and settings map. | None | Spryker\Zed\Configuration\Communication\Console |
| ConfigurationApplicationPlugin | Registers the Configuration facade as an application service for direct access in Zed applications. | None | Spryker\Zed\Configuration\Communication\Plugin\Application |

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

Add the import statements:

```php
use Spryker\Zed\Configuration\Communication\Console\ConfigurationSyncConsole;
use Spryker\Zed\Configuration\Communication\Plugin\Application\ConfigurationApplicationPlugin;
```

Register the console command in `getConsoleCommands()`:

```php
protected function getConsoleCommands(Container $container): array
{
    $commands = [
        // ...
        new ConfigurationSyncConsole(),
        // ...
    ];
}
```

Register the application plugin in `getApplicationPlugins()`:

```php
protected function getApplicationPlugins(Container $container): array
{
    $applicationPlugins = parent::getApplicationPlugins($container);
    // ...
    $applicationPlugins[] = new ConfigurationApplicationPlugin();

    return $applicationPlugins;
}
```

**src/Pyz/Zed/Application/ApplicationDependencyProvider.php**

Add the import statement:

```php
use Spryker\Zed\Configuration\Communication\Plugin\Application\ConfigurationApplicationPlugin;
```

Register the application plugin in each Zed application context (`getBackofficeApplicationPlugins()`, `getBackendGatewayApplicationPlugins()`, `getBackendApiApplicationPlugins()`):

```php
protected function getBackofficeApplicationPlugins(): array
{
    return [
        // ...
        new ConfigurationApplicationPlugin(),
    ];
}

protected function getBackendGatewayApplicationPlugins(): array
{
    return [
        // ...
        new ConfigurationApplicationPlugin(),
    ];
}

protected function getBackendApiApplicationPlugins(): array
{
    return [
        // ...
        new ConfigurationApplicationPlugin(),
    ];
}
```

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

1. Run `console configuration:sync` and verify that it outputs the number of processed settings.
2. Verify that configuration values can be read in Zed context via the Client layer without going through storage.

{% endinfo_block %}

#### 6.2) Set up queue configuration

Register the Configuration storage synchronization queue in both message broker implementations.

**src/Pyz/Client/RabbitMq/RabbitMqConfig.php**

Add the import statement:

```php
use Spryker\Shared\Configuration\ConfigurationConstants;
```

Add the queue name to `getSynchronizationQueueConfiguration()`:

```php
protected function getSynchronizationQueueConfiguration(): array
{
    return [
        // ...
        ConfigurationConstants::QUEUE_NAME_SYNC_CONFIGURATION,
    ];
}
```

**src/Pyz/Client/SymfonyMessenger/SymfonyMessengerConfig.php**

Add the import statement:

```php
use Spryker\Shared\Configuration\ConfigurationConstants;
```

Add the queue name to `getSynchronizationQueueConfiguration()`:

```php
protected function getSynchronizationQueueConfiguration(): array
{
    return [
        // ...
        ConfigurationConstants::QUEUE_NAME_SYNC_CONFIGURATION,
    ];
}
```

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

Run `console queue:setup` and verify that the `sync.storage.configuration` queue is created in RabbitMQ.

{% endinfo_block %}

#### 6.3) Register queue message processor

Register the synchronization storage queue message processor for the Configuration sync queue:

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

Add the import statements:

```php
use Spryker\Shared\Configuration\ConfigurationConstants;
```

Add the processor to `getProcessorMessagePlugins()`:

```php
protected function getProcessorMessagePlugins(Container $container): array
{
    return [
        // ...
        ConfigurationConstants::QUEUE_NAME_SYNC_CONFIGURATION =&gt; new SynchronizationStorageQueueMessageProcessorPlugin(),
    ];
}
```

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

1. Save a configuration value in the Back Office.
2. Run `console queue:worker:start --stop-when-empty`.
3. Verify that the `sync.storage.configuration` queue is processed without errors.

{% endinfo_block %}

#### 6.4) Register publisher plugins

Enable Publish &amp; Synchronize for configuration values by registering the publisher plugin:

| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
| --- | --- | --- | --- |
| ConfigurationValueWritePublisherPlugin | Publishes storefront-visible, non-secret configuration values to `spy_configuration_storage` when `spy_configuration_value` entities are created, updated, or deleted. | None | Spryker\Zed\Configuration\Communication\Plugin\Publisher |

**src/Pyz/Zed/Publisher/PublisherDependencyProvider.php**

Add the import statement:

```php
use Spryker\Zed\Configuration\Communication\Plugin\Publisher\ConfigurationValueWritePublisherPlugin;
```

Register the plugin group in `getPublisherPlugins()`:

```php
protected function getPublisherPlugins(): array
{
    return array_merge(
        // ...
        $this-&gt;getConfigurationStoragePlugins(),
    );
}
```

Add the new method:

```php
/**
 * @return list&lt;\Spryker\Zed\PublisherExtension\Dependency\Plugin\PublisherPluginInterface&gt;
 */
protected function getConfigurationStoragePlugins(): array
{
    return [
        new ConfigurationValueWritePublisherPlugin(),
    ];
}
```

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

1. Save a configuration value in the Back Office.
2. Check that the `spy_configuration_storage` table contains the published value.
3. Verify that secret settings are NOT published to storage.

{% endinfo_block %}

#### 6.5) Register scope identifier provider plugins

Register the store scope identifier provider to resolve the current store name as the scope identifier:

| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
| --- | --- | --- | --- |
| StoreConfigurationScopeIdentifierProviderPlugin | Provides the current store name as the scope identifier for the `store` scope. | Store module installed | Spryker\Zed\Store\Communication\Plugin\Configuration |

**src/Pyz/Zed/Configuration/ConfigurationDependencyProvider.php**

```php
&lt;?php

/**
 * This file is part of the Spryker Suite.
 * For full license information, please view the LICENSE file that was distributed with this source code.
 */

declare(strict_types = 1);

namespace Pyz\Zed\Configuration;

use Spryker\Zed\Configuration\ConfigurationDependencyProvider as SprykerConfigurationDependencyProvider;
use Spryker\Zed\Store\Communication\Plugin\Configuration\StoreConfigurationScopeIdentifierProviderPlugin;

class ConfigurationDependencyProvider extends SprykerConfigurationDependencyProvider
{
    /**
     * @return array&lt;\Spryker\Zed\ConfigurationExtension\Dependency\Plugin\ConfigurationScopeIdentifierProviderPluginInterface&gt;
     */
    protected function getScopeIdentifierProviderPlugins(): array
    {
        return [
            new StoreConfigurationScopeIdentifierProviderPlugin(),
        ];
    }
}
```

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

1. Navigate to the Configuration Management page in the Back Office.
2. Switch the scope selector to a specific store.
3. Verify that the scope identifier resolves to the store name (for example `DE`, `AT`).

{% endinfo_block %}

#### 6.6) Register Client-level request expander plugins

Register the store scope expander to attach the current store scope to configuration value requests:

| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
| --- | --- | --- | --- |
| StoreScopeConfigurationValueRequestExpanderPlugin | Expands the configuration value request with the current store scope and store name as scope identifier. | Store module installed | Spryker\Client\Store\Plugin\Configuration |

**src/Pyz/Client/Configuration/ConfigurationDependencyProvider.php**

```php
&lt;?php

/**
 * This file is part of the Spryker Suite.
 * For full license information, please view the LICENSE file that was distributed with this source code.
 */

declare(strict_types = 1);

namespace Pyz\Client\Configuration;

use Spryker\Client\Configuration\ConfigurationDependencyProvider as SprykerConfigurationDependencyProvider;
use Spryker\Client\Store\Plugin\Configuration\StoreScopeConfigurationValueRequestExpanderPlugin;

class ConfigurationDependencyProvider extends SprykerConfigurationDependencyProvider
{
    protected function getConfigurationValueRequestExpanderPlugins(): array
    {
        return [
            new StoreScopeConfigurationValueRequestExpanderPlugin(),
        ];
    }
}
```

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

1. Read a configuration value via the Client layer.
2. Verify that the request includes the current store as a scope with the store name as scope identifier.

{% endinfo_block %}

#### 6.7) Optional: Set up data import

Enable CLI-based bulk import of configuration values from CSV files.

| PLUGIN | SPECIFICATION | PREREQUISITES | NAMESPACE |
| --- | --- | --- | --- |
| ConfigurationValueDataImportPlugin | Imports configuration values from CSV using the DataImport framework. Validates setting keys, scopes, and constraints. Skips secret settings with a warning. | `configuration:sync` must be run first | Spryker\Zed\Configuration\Communication\Plugin\DataImport |

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

Add the import statement and register the plugin in `getDataImporterPlugins()`:

```php
use Spryker\Zed\Configuration\Communication\Plugin\DataImport\ConfigurationValueDataImportPlugin;
```

```php
protected function getDataImporterPlugins(): array
{
    return [
        // ...
        new ConfigurationValueDataImportPlugin(),
    ];
}
```

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

Add the import type to the full import types list:

```php
use Spryker\Zed\Configuration\ConfigurationConfig;
```

```php
public function getFullImportTypes(): array
{
    return [
        // ...
        ConfigurationConfig::IMPORT_TYPE_CONFIGURATION_VALUE,
    ];
}
```

**data/import/common/common/configuration_value.csv**

Create the CSV file with the required columns:

```csv
setting_key,scope,scope_identifier,value
```

**data/import/local/full_EU.yml** (or your region-specific import config)

Add the `configuration-value` data entity:

```yaml
actions:
    # ...
    - data_entity: configuration-value
      source: data/import/common/common/configuration_value.csv
```

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

1. Add a row to `data/import/common/common/configuration_value.csv` with a valid setting key, for example:

   ```csv
   setting_key,scope,scope_identifier,value
   system:general:basic:site_name,store,DE,My German Store
   ```

2. Run `console data:import configuration-value`.
3. Verify the value is saved by checking the Back Office Configuration page.

{% endinfo_block %}

### 7) Add install recipe commands

Add the `configuration:sync` command to the install recipes so that the merged schema and settings map are generated during deployment.

The generated files are written to `data/configuration/` inside the container and are excluded from both the Git repository and the Docker image. A freshly deployed container therefore never has them until `configuration:sync` runs. Without these files, the settings map is empty and every configuration value resolves to `null` at runtime—even though the stored values remain intact in the `spy_configuration_value` table. Add the command to every recipe a deployment invokes, not only to the local build recipe.

#### 7.1) Add the command to the local build recipe

**config/install/docker.yml**

Add the command to the `build` section:

```yaml
    build:
        # ... existing commands ...

        configuration-sync:
            command: &apos;vendor/bin/console configuration:sync&apos;
```

#### 7.2) Add the command to the cloud deployment recipes

Add a `configuration` section to each recipe your deployment pipeline invokes. Place it after the database migration steps and before any `data:import` step: the imported configuration-value rows are validated against the settings map, and the import aborts when the map is missing.

| RECIPE | DEPLOYMENT HOOK |
| --- | --- |
| config/install/production.yml | `SPRYKER_HOOK_INSTALL` |
| config/install/destructive.yml | `SPRYKER_HOOK_DESTRUCTIVE_INSTALL` |
| config/install/dynamic-store.yml | Dynamic Multistore deployments |

Add the following section to each of the preceding recipes:

```yaml
    configuration:
        configuration-sync:
            command: &apos;vendor/bin/console configuration:sync -vvv --no-ansi&apos;
```

If your project clones new environments from a deploy file template, such as `deploy.aws-env-template.yml`, add the section to the template as well so that new environments inherit it.

{% info_block infoBox &quot;pre-deploy recipes&quot; %}

Do not add `configuration:sync` to `config/install/pre-deploy.yml`. This recipe runs before the new code and the database migration are in place, so there is nothing to sync against yet.

{% endinfo_block %}

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

Running `configuration:sync` cannot overwrite values set by users. The command only writes the two generated cache files and never opens a database connection, so it is safe to run on every deployment.

{% endinfo_block %}

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

Run each install recipe and verify that the `configuration:sync` step executes without errors.

{% endinfo_block %}

### 8) Configure data directory

Add the `data/configuration/` directory to `.gitignore` exceptions to ensure the generated schema files are tracked:

**.gitignore**

```diff
 /data/*
 !/data/import/
 !/data/export/
+!/data/configuration/
```

Create the directory with a `.gitkeep` file:

```bash
mkdir -p data/configuration
touch data/configuration/.gitkeep
```

Add the `data/configuration/` directory to `.dockerignore` exceptions to ensure the directory is included in the Docker build context and reaches the image:

**.dockerignore**

```diff
 /data
 !/data/import
 !/data/export
+!/data/configuration
```

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

Run `console configuration:sync` and verify that `data/configuration/merged-schema.php` and `data/configuration/settings-map.php` are generated.

{% endinfo_block %}

## Configuration Schema YAML Reference

Configuration settings are defined in YAML files with the `*.configuration.yml` extension. The schema sync command (`configuration:sync`) discovers these files and merges them into a single schema.
</description>
            <pubDate>Tue, 25 Aug 2026 07:19:33 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/integrate-and-configure/integrate-confguration-feature.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/integrate-and-configure/integrate-confguration-feature.html</guid>
            
            
        </item>
        
        <item>
            <title>Workflows, Skills, and Agents</title>
            <description>&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;
&lt;p&gt;The AI Dev SDK ships five ready-made workflows, more than 30 skills, and six agents that codify common Spryker practice — from turning a demoshop clone into a customer project, to building features, fixing bugs, and upgrading releases. Instead of teaching your AI assistant Spryker’s conventions prompt by prompt, you install workflows that already know them.&lt;/p&gt;
&lt;p&gt;The skills and agents reach your project in two ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Via the &lt;code&gt;ai-dev:setup&lt;/code&gt; console command&lt;/strong&gt; — copies the skills and agents into your project’s AI-tool directories (&lt;code&gt;.claude/skills/&lt;/code&gt; + &lt;code&gt;.claude/agents/&lt;/code&gt;, &lt;code&gt;.windsurf/skills/&lt;/code&gt; + &lt;code&gt;.windsurf/agents/&lt;/code&gt;, and so on) for every AI tool listed under &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-installation.html#generated-files-per-ai-tool&quot;&gt;Generated files per AI tool&lt;/a&gt;. Codex CLI has no agents directory, so agents are skipped for that tool.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Via the Claude Code plugin&lt;/strong&gt; — for Claude Code users, the &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-claude-code.html&quot;&gt;Claude Code plugin&lt;/a&gt; installs the same skills and agents through the official marketplace, no console command required.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both delivery paths read from the same source files — the plugin just packages them for marketplace installation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What’s the difference between a skill and an agent?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Skills&lt;/strong&gt; load into the active chat on demand. Use them when you want the assistant to follow a specific workflow (write a product requirement document, refresh caches, run QA).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agents&lt;/strong&gt; are isolated sub-conversations the assistant delegates to. Use them when you want focused, single-purpose work done in its own context window (verify a behavior, diagnose a failure).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You do not need to remember the names — the assistant picks the right skill or agent from your prompt. The tables below explain what each one does so you know what to expect.&lt;/p&gt;
&lt;h2 id=&quot;available-workflows-and-wizards&quot;&gt;Available workflows and wizards&lt;/h2&gt;
&lt;p&gt;Five shipped workflows cover the work most projects need on day one, so you get value before you write anything of your own. Each has its own page, listed under this one in the sidebar:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workflow&lt;/th&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-project-starter-wizard.html&quot;&gt;Project Starter Wizard&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;project-starter-wizard&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Turns a fresh demoshop clone into your project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-customization-workflow.html&quot;&gt;Customization Workflow&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;spryker-customization&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Builds a feature from a product requirement document to a committed branch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-bugfix-workflow.html&quot;&gt;Bugfix Workflow&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;spryker-bugfix&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Drives a bug to a validated, QA-accepted fix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-upgrade-workflow.html&quot;&gt;Upgrade Workflow&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;spryker-upgrade&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upgrades a customized project to a newer Spryker release&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-profiler-workflow.html&quot;&gt;Profiler Workflow&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;spryker-profiler&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reads the Spryker WebProfiler as hard performance numbers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The first four are orchestrators: they own a full workflow and delegate each stage to the specialist skills and agents listed below. Alongside them, &lt;code&gt;product-requirement-document&lt;/code&gt; turns a feature idea into a research-grounded spec, so you can go from idea to specification to implementation without leaving your assistant.&lt;/p&gt;
&lt;h2 id=&quot;composable-by-design&quot;&gt;Composable by design&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-dev/skills.png&quot; alt=&quot;composable-skills&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Each skill covers one stage of work and nothing more — research a feature, seed test data, run static validation, drive the running app, capture screenshots. Nothing is bundled into a single monolithic assistant, so you compose the stages into whatever long-running workflow your team actually runs.&lt;/p&gt;
&lt;p&gt;This is how the orchestrator skills themselves are built. &lt;code&gt;spryker-bugfix&lt;/code&gt; does not reimplement testing or validation — it calls &lt;code&gt;codecept-functional&lt;/code&gt;, &lt;code&gt;static-validation&lt;/code&gt;, &lt;code&gt;code-review&lt;/code&gt;, and &lt;code&gt;spryker-qa-coverage&lt;/code&gt; in sequence, and delegates isolated work to the agents. Your own workflows compose the same building blocks the same way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Adapt the shipped workflows to your process.&lt;/strong&gt; The four orchestrators are a baseline, not a prescription. Every team runs a different process — different review gates, different definition of done, different tracker. Because the stages are separate, you can reorder them, skip the ones you do not need, or drop your own stage in between. Ask for the workflow you want in your own words and the assistant assembles it from the installed skills.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use the shipped skills as a template for your own.&lt;/strong&gt; Every skill is plain Markdown with frontmatter, and every one ships a README documenting its flow, design decisions, and limits — linked in the &lt;strong&gt;Reference&lt;/strong&gt; column of the tables below. Read the skill closest to what you need, then write your own the same way: your team’s release checklist, your onboarding walkthrough, your project-specific conventions. The result is AI-assisted development shaped by how your team works rather than by a fixed set of commands.&lt;/p&gt;
&lt;h2 id=&quot;how-to-invoke&quot;&gt;How to invoke&lt;/h2&gt;
&lt;p&gt;In most cases you do not need to call a skill or agent by name. Describe what you want — &lt;em&gt;“build this feature”&lt;/em&gt;, &lt;em&gt;“verify the new fee shows on the cart”&lt;/em&gt;, &lt;em&gt;“write a product requirement document for…”&lt;/em&gt; — and the assistant matches your wording to the right skill or agent from the set installed in your project.&lt;/p&gt;
&lt;p&gt;If you want to invoke one explicitly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Claude Code plugin&lt;/strong&gt; — every skill is also available as a slash command. Type &lt;code&gt;/spryker-ai-dev-sdk:&amp;lt;skill-name&amp;gt;&lt;/code&gt; (for example &lt;code&gt;/spryker-ai-dev-sdk:spryker-customization&lt;/code&gt;) and the skill loads into the current chat. The command naming rule is on the &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-claude-code.html#skills&quot;&gt;Claude Code&lt;/a&gt; page.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Other AI tools&lt;/strong&gt; — invocation follows the tool’s own convention for skills and agents. In Cursor, Windsurf, Copilot, OpenCode, and Codex CLI, the skills and agents land in the tool-specific directories (&lt;code&gt;.cursor/&lt;/code&gt;, &lt;code&gt;.windsurf/&lt;/code&gt;, etc.) and are picked up automatically by your assistant. &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-installation.html#generated-files-per-ai-tool&quot;&gt;Generated files per AI tool&lt;/a&gt; lists the output paths per tool.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agents (subagents)&lt;/strong&gt; are not invoked by the user directly — they are spawned by the assistant or by a parent skill when the work calls for an isolated context. You can suggest one by name in your prompt (&lt;em&gt;“use the spryker-verifier to…”&lt;/em&gt;), but the assistant decides when to spawn.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;skills&quot;&gt;Skills&lt;/h2&gt;
&lt;p&gt;Skills are delivered through &lt;code&gt;ai-dev:setup&lt;/code&gt; (all supported AI tools) or the Claude Code plugin.&lt;/p&gt;
&lt;p&gt;Every skill ships a README in its own directory in the plugin repository, covering its full flow, design decisions, and known limits. The &lt;strong&gt;Reference&lt;/strong&gt; column links to it — use it when the summary here is not enough.&lt;/p&gt;
&lt;h3 id=&quot;orchestrator-skills&quot;&gt;Orchestrator skills&lt;/h3&gt;
&lt;p&gt;These skills own a full workflow and delegate each stage to the specialist skills and agents below. Each has its own page.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Benefits&lt;/th&gt;
&lt;th&gt;Reference&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-project-starter-wizard.html&quot;&gt;&lt;code&gt;project-starter-wizard&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Turn a fresh clone of a demoshop into the customer’s project&lt;/td&gt;
&lt;td&gt;One developer interview up front, then nine orchestrated steps to a verified running shop; a resumable state file survives interruptions&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/project-starter-wizard/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-customization-workflow.html&quot;&gt;&lt;code&gt;spryker-customization&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Walk a product requirement document or set of acceptance criteria through to a committed branch&lt;/td&gt;
&lt;td&gt;One workflow drives the full build; quality bar (PoC or MVP) chosen up-front; never auto-commits&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/spryker-customization/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-bugfix-workflow.html&quot;&gt;&lt;code&gt;spryker-bugfix&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Drive a bug from an optional tracker ticket or a plain description through to a committed, validated, QA-accepted fix&lt;/td&gt;
&lt;td&gt;Orchestrates reproduce, root cause, minimal fix, functional test, static validation, review, QA, and final verification; a shared attempt budget loops back on any failed gate; Autonomous mode adds a pushed draft PR with a remote CI watch loop&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/spryker-bugfix/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-upgrade-workflow.html&quot;&gt;&lt;code&gt;spryker-upgrade&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Upgrade the project’s modules and features to a newer Spryker release&lt;/td&gt;
&lt;td&gt;Checks first whether the customizations are covered by tests at all, resolves the constraint blockers that stop a release-group bump, then detects the silent damage a heavily customized project would otherwise ship&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/spryker-upgrade/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;project-setup-skills&quot;&gt;Project setup skills&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;project-starter-wizard&lt;/code&gt; runs these as its steps, and each also works standalone on an existing project.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Benefits&lt;/th&gt;
&lt;th&gt;Reference&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;project-ci-generator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transform an inherited product-style CI setup into a single, lean project CI pipeline&lt;/td&gt;
&lt;td&gt;Reads the CI that actually exists rather than applying a template; proposes a keep/drop plan for approval before deleting anything; ports the same jobs to GitLab or Bitbucket&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/project-ci-generator/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;configure-codebase&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Register a custom namespace instead of &lt;code&gt;Pyz&lt;/code&gt; and wire autoload, frontend build, and Codeception to resolve it&lt;/td&gt;
&lt;td&gt;One pass covers every place the namespace must be declared, so the project builds, lints, and tests under its own name&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/configure-codebase/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;brand-project&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Apply or change the project’s brand identity — name, development domain, Docker namespace, palette, and logo&lt;/td&gt;
&lt;td&gt;Repeatable any time, not only at project start; works pre- or post-boot&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/brand-project/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;configure-services&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Change what infrastructure the project runs on, or build a new environment deploy file&lt;/td&gt;
&lt;td&gt;Surgical edits to the keys it owns; neighboring deploy-file blocks stay untouched&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/configure-services/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;define-stores&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create or redefine a DMS project’s stores and region before the first boot&lt;/td&gt;
&lt;td&gt;Clears the hardcoded store and locale literals that otherwise abort the boot&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/define-stores/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;project-data&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Populate, reshape, reduce, clean up, or remove the project’s import data&lt;/td&gt;
&lt;td&gt;One skill for every &lt;code&gt;data/import&lt;/code&gt; change — adapt the demo shop to your stores, generate a catalog from images, or start with no demo data at all&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/project-data/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-import-tools&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read, filter, edit, and validate data import CSV files and manifests&lt;/td&gt;
&lt;td&gt;Reliable where shell tools corrupt multi-line quoted fields; static validation catches boot-aborting data in seconds&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/spryker-import-tools/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;boot-and-verify&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Take a transformed project from “files written” to “verified running”&lt;/td&gt;
&lt;td&gt;Per-store verification of storefront, Back Office, search, and queues; an independent verifier agent gives the verdict&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/boot-and-verify/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;curate-golive-data&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Make the data the project keeps production-safe before go-live&lt;/td&gt;
&lt;td&gt;Resolves the go-live warnings the data and boot steps surface — placeholder tax rates, Spryker CDN imagery, demo accounts&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/curate-golive-data/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;translate-content&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Translate storefront content into a project locale&lt;/td&gt;
&lt;td&gt;Strictly per-locale and opt-in; covers glossary as well as catalog, CMS, navigation, and labels&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/translate-content/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;development-skills&quot;&gt;Development skills&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Benefits&lt;/th&gt;
&lt;th&gt;Reference&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ai-dev-setup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Generate rules, an agents/context file, and skills for the project and the chosen AI tool&lt;/td&gt;
&lt;td&gt;One command sets up consistent AI tooling for the whole team; also refreshes the rules and context file from the latest upstream content&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/ai-dev-setup/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;code-review&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Review staged or PR changes against Spryker coding standards&lt;/td&gt;
&lt;td&gt;Catches Spryker-specific issues before they reach a pull request&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/code-review/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;propel-schema&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create and modify Propel ORM schema files&lt;/td&gt;
&lt;td&gt;Follows Spryker schema conventions automatically&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/propel-schema/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data-import&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create and modify data import CSV files and importers&lt;/td&gt;
&lt;td&gt;Generates importers that fit Spryker’s data-import path&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/data-import/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;static-validation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run static analysis over only the code that changed against a base branch — PHP and frontend&lt;/td&gt;
&lt;td&gt;Validates the diff rather than the whole project; groups PHP by changed file or by whole changed module&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/static-validation/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;payment-template&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Scaffold payment method integration&lt;/td&gt;
&lt;td&gt;Follows Spryker payment module patterns end-to-end&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/payment-template/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;yves-atomic-frontend&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create atomic design components for the Yves storefront&lt;/td&gt;
&lt;td&gt;Components match the project’s atomic conventions&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/yves-atomic-frontend/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;product-requirement-document&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Turn a feature idea into a research-grounded product requirement document before any code is written&lt;/td&gt;
&lt;td&gt;Spec-before-code; assigns a real Spryker actor to every story; cuts ambiguity before implementation&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/product-requirement-document/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-refresher&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run the right post-change console and composer commands after edits&lt;/td&gt;
&lt;td&gt;Owns the file-to-command mapping (codegen, caches, frontend builds, class-resolver); no missed cache rebuilds&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/spryker-refresher/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-docs-research&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Look up the right answer in official Spryker documentation&lt;/td&gt;
&lt;td&gt;Grounds AI work in documented behavior rather than the model’s memory; falls back gracefully when MCP tools are unavailable&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/spryker-docs-research/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-runtime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Drive the running Spryker application — Yves, Back Office, Merchant Portal, console, HTTP&lt;/td&gt;
&lt;td&gt;Real authenticated sessions; read-only DB, Redis, and queue inspection; reusable building block for higher-level skills and agents&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/spryker-runtime/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ai-runtime-debugging&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Inspect Spryker runtime state safely from an AI session&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[AI-DEBUG]&lt;/code&gt; tagged-log pattern plus optional XDebug step-debug; built-in cleanup of debug instrumentation before commit&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/ai-runtime-debugging/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;testing-and-performance-skills&quot;&gt;Testing and performance skills&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Benefits&lt;/th&gt;
&lt;th&gt;Reference&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;codecept-functional&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Generate Codeception functional tests&lt;/td&gt;
&lt;td&gt;Tests follow Spryker test patterns out of the box&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/codecept-functional/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cypress-tests&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create, run, review, and validate Cypress end-to-end tests&lt;/td&gt;
&lt;td&gt;Day-to-day E2E work against your project’s own suite — storefront, Back Office, Merchant Portal, and Glue API&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/cypress-tests/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cypress-migration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Replace Spryker’s demoshop test suites with a project-owned Cypress baseline&lt;/td&gt;
&lt;td&gt;One-time migration; vendors in a proven reference implementation, wires up CI, and generates the companion &lt;code&gt;cypress-tests&lt;/code&gt; skill for the project&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/cypress-migration/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-qa-coverage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Turn acceptance criteria into a four-bucket test plan executed against the live app&lt;/td&gt;
&lt;td&gt;Coverage goes beyond literal acceptance criteria — happy, negative, authorization, and corner cases; reports pass/fail with real evidence&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/spryker-qa-coverage/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-profiler-workflow.html&quot;&gt;&lt;code&gt;spryker-profiler&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Read and configure the Spryker WebProfiler — every metric it recorded about a request&lt;/td&gt;
&lt;td&gt;Real measurements instead of guesses; finds N+1 duplicates, Redis and search call counts, and the heaviest request; also fixes a profiler that shows no data&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/spryker-profiler/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;agents&quot;&gt;Agents&lt;/h2&gt;
&lt;p&gt;Agents are delivered through &lt;code&gt;ai-dev:setup&lt;/code&gt; (every supported AI tool with an agents directory) or the Claude Code plugin.&lt;/p&gt;
&lt;p&gt;Each agent does one narrowly defined job in its own context and reports back — the “never” boundaries in the table are what make the results trustworthy.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Benefits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-code-reviewer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Review code against Spryker’s coding standards and architectural rules&lt;/td&gt;
&lt;td&gt;Deeper, Spryker-aware review than a generic linter; catches layer-architecture and module-convention issues&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-feature-expert&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Answer “how does feature X work in this project / in Spryker”&lt;/td&gt;
&lt;td&gt;Pushes back when the user’s framing reinvents an existing primitive; surfaces canonical patterns first; never edits code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-verifier&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Verify that a specific behavior holds in the running Spryker environment&lt;/td&gt;
&lt;td&gt;Returns PASS / FAIL / BLOCKED per acceptance criterion with raw evidence; never lies green; never tries to fix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-issue-diagnoser&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Investigate why something failed and return a root cause&lt;/td&gt;
&lt;td&gt;Reads logs, DB state, queue, search, browser console — returns a suggested direction; never attempts the fix itself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-data-seeder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create small additive test data through Spryker’s existing import path&lt;/td&gt;
&lt;td&gt;Safe and incremental — CSV + &lt;code&gt;data:import&lt;/code&gt; only; never edits code, never writes directly to the database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-screenshot-collector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Capture screenshots and GIFs of pages and flows for demos and documentation&lt;/td&gt;
&lt;td&gt;Pure capture — never asserts whether something works, never investigates failures, never edits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;related&quot;&gt;Related&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev.html&quot;&gt;AI Dev SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-installation.html&quot;&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-mcp-server.html&quot;&gt;AI Dev MCP Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-claude-code.html&quot;&gt;Claude Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-project-starter-wizard.html&quot;&gt;Project Starter Wizard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-customization-workflow.html&quot;&gt;Customization Workflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-bugfix-workflow.html&quot;&gt;Bugfix Workflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-upgrade-workflow.html&quot;&gt;Upgrade Workflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-profiler-workflow.html&quot;&gt;Profiler Workflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
            <pubDate>Mon, 24 Aug 2026 13:00:28 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/ai/ai-dev/ai-dev-workflows-skills-and-agents.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/ai/ai-dev/ai-dev-workflows-skills-and-agents.html</guid>
            
            
        </item>
        
        <item>
            <title>Upgrade Workflow</title>
            <description>&lt;h2 id=&quot;availability&quot;&gt;Availability&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;spryker-upgrade&lt;/code&gt; skill is available from &lt;code&gt;spryker-sdk/ai-dev&lt;/code&gt; version 0.6.4, which ships version 0.4.0 of the &lt;code&gt;spryker-ai-dev-sdk&lt;/code&gt; Claude Code plugin.&lt;/p&gt;
&lt;p&gt;To update the Claude Code plugin, run &lt;code&gt;/plugin&lt;/code&gt; in Claude Code and update &lt;code&gt;spryker-ai-dev-sdk&lt;/code&gt; from the &lt;code&gt;spryker-plugins-official&lt;/code&gt; marketplace to version 0.4.0 or later. For installation instructions, see &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-claude-code.html&quot;&gt;Claude Code&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-the-skill-does&quot;&gt;What the skill does&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-dev/upgrade.png&quot; alt=&quot;upgrade&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;spryker-upgrade&lt;/code&gt; upgrades your project’s modules and features to a newer Spryker release. It is built for projects with heavy &lt;code&gt;src/Pyz&lt;/code&gt; customization, where the dangerous part of an upgrade is not the code that stops compiling but the code that keeps running while doing nothing.&lt;/p&gt;
&lt;p&gt;The skill combines an orchestrated workflow with a set of deterministic detector scripts. Each script is a standalone CLI that runs on host PHP using reflection or static parsing only, with no Spryker bootstrap — so they also work in CI.&lt;/p&gt;
&lt;p&gt;Invoke it with &lt;em&gt;“upgrade the project”&lt;/em&gt;, &lt;em&gt;“update to the latest release”&lt;/em&gt;, or any request to bump &lt;code&gt;spryker-feature/*&lt;/code&gt; or &lt;code&gt;spryker/*&lt;/code&gt; packages.&lt;/p&gt;
&lt;h2 id=&quot;why-silent-damage-is-the-real-problem&quot;&gt;Why silent damage is the real problem&lt;/h2&gt;
&lt;p&gt;Everything a Spryker project overrides fails quietly when core moves. A dead override still loads, a replaced plugin stack still boots, and a stale template still renders. Nothing errors — the customization simply stops taking effect.&lt;/p&gt;
&lt;p&gt;The workflow therefore starts with a different question than “which modules changed”. It asks which of your customizations would notice if they stopped working, and whether the upgrade can be verified at all.&lt;/p&gt;
&lt;h2 id=&quot;the-phases&quot;&gt;The phases&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Coverage check&lt;/strong&gt; — measures your override surface against the tests that cover it, and offers to write characterization tests for the gaps. These must be written before the upgrade; written afterwards they pin the upgraded behavior and can no longer detect that it changed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Preflight and baselines&lt;/strong&gt; — records snapshots of overrides and shadowed frontend files, checks for constraint styles that block resolution, and captures pre-existing damage so it is not misattributed to the upgrade.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Constraint resolution&lt;/strong&gt; — relaxes patch-locked constraints, then resolves conflicts iteratively. Conflicts arrive in waves, each root bump revealing the next transitive layer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Detection&lt;/strong&gt; — after Composer completes, runs the detectors that find the silent damage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resolution&lt;/strong&gt; — merges shadowed files, rewires replaced plugin stacks, fixes broken configuration references, and requires a migration guide for every major module bump.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;what-the-detectors-find&quot;&gt;What the detectors find&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Detector&lt;/th&gt;
&lt;th&gt;Damage it catches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check-test-coverage.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Customizations with no test over them, ranked by risk, with the test type that would catch each gap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check-vendor-class-replacement.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Project files that declare a vendor namespace and replace a core class outright, so the vendor implementation never loads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check-constraint-style.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Patch-locked or exactly pinned constraints that make a feature bump unresolvable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check-typed-members.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Untyped overrides of constants and properties that core has since typed — a fatal on class load that aborts the console itself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check-dead-overrides.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Project methods overriding a vendor method that the new version deleted, so the project logic stops being called&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;twig-shadow-map.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Shadowed Twig, SCSS, and TypeScript files whose vendor counterparts changed upstream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;merge-shadowed-files.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runs a three-way merge for every shadowed file and sorts the outcomes into clean, identical, conflicted, and removed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check-plugin-usage.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Vendor plugins that are missing or deprecated after the upgrade, and project plugins implementing a removed interface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check-config-constants.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Configuration referencing a vendor constants interface or constant that no longer exists, which breaks bootstrap of every application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;list-major-bumps.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every major module bump, with a documentation search URL, changelog URL, and compare URL per package&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;resolve-constraints.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runs Composer, parses root conflicts, raises the constraints the tree demands, and repeats&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;unpin-feature-driven-modules.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Breaks a cohort deadlock, where a group of modules sharing a dependency must move together&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The detectors keep their snapshots and reports in &lt;code&gt;.spryker-upgrade/state/&lt;/code&gt; inside the project, created self-gitignoring on first use.&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;Order matters&lt;/div&gt;
&lt;p&gt;The dependency between the detectors is real. The skill runs them in the correct order — coverage and replacement checks first, then baselines and constraint preflight, then resolution, then post-Composer detection. Running them out of order wastes time on phantom findings.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;h2 id=&quot;run-the-detectors-in-ci&quot;&gt;Run the detectors in CI&lt;/h2&gt;
&lt;p&gt;Two detectors are cheap enough to gate every pull request, and both catch damage that is otherwise invisible until runtime:&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;php &lt;span class=&quot;nv&quot;&gt;$UP&lt;/span&gt;/check-typed-members.php     &lt;span class=&quot;c&quot;&gt;# fatals on class load&lt;/span&gt;
php &lt;span class=&quot;nv&quot;&gt;$UP&lt;/span&gt;/check-plugin-usage.php      &lt;span class=&quot;c&quot;&gt;# missing plugins&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Neither needs a snapshot, a database, or a search backend — only an installed &lt;code&gt;vendor/&lt;/code&gt; directory. Failing the build on exit code 1 turns “the Back Office returns 500 after deploy” into a red pipeline.&lt;/p&gt;
&lt;p&gt;For dependency-bumping pull requests, also wrap the change in a snapshot and verify pair for &lt;code&gt;check-dead-overrides.php&lt;/code&gt; and &lt;code&gt;twig-shadow-map.php&lt;/code&gt;, because those compare against pre-upgrade state and cannot work from a single point in time.&lt;/p&gt;
&lt;p&gt;You can also gate &lt;code&gt;check-test-coverage.php&lt;/code&gt; in CI, but gate it on a ratchet rather than on zero: fail when a pull request increases the number of untested overrides. New customization then arrives with a test, instead of the project needing a coverage project before anyone can merge.&lt;/p&gt;
&lt;h2 id=&quot;known-limits&quot;&gt;Known limits&lt;/h2&gt;
&lt;p&gt;No detector covers Propel schema merges, glossary keys, ACL and navigation for new Back Office routes, or pure behavioral change. Those remain process gates and tests, and the skill marks them as such.&lt;/p&gt;
&lt;p&gt;Published migration guides are also sometimes stale or absent, so the skill treats the tag diff, not the guide, as the source of truth.&lt;/p&gt;
&lt;h2 id=&quot;requirements&quot;&gt;Requirements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A Spryker project with Composer installed, and host PHP available to run the detector scripts&lt;/li&gt;
&lt;li&gt;An AI tool with the SDK’s skills loaded — either through the &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-claude-code.html&quot;&gt;Claude Code plugin&lt;/a&gt; or via &lt;code&gt;ai-dev:setup&lt;/code&gt; for another supported tool&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related&quot;&gt;Related&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/project-setup-wizard/plugins/spryker-ai-dev-sdk/skills/spryker-upgrade/README.md&quot;&gt;&lt;code&gt;spryker-upgrade&lt;/code&gt; README&lt;/a&gt; — the skill’s own reference in the plugin repository&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-workflows-skills-and-agents.html&quot;&gt;Workflows, Skills, and Agents&lt;/a&gt; — the full reference of every skill and agent&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/upgrade-and-migrate/upgrade-and-migrate.html&quot;&gt;Upgrade and migrate&lt;/a&gt; — the manual upgrade and migration guides&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-installation.html&quot;&gt;Installation&lt;/a&gt; — install the SDK and generate your project’s rules, context file, and skills&lt;/li&gt;
&lt;/ul&gt;
</description>
            <pubDate>Mon, 24 Aug 2026 13:00:28 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/ai/ai-dev/ai-dev-upgrade-workflow.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/ai/ai-dev/ai-dev-upgrade-workflow.html</guid>
            
            
        </item>
        
        <item>
            <title>Project Starter Wizard</title>
            <description>&lt;p&gt;Every Spryker project starts from a demoshop — a complete, working reference shop that you reshape into your customer’s project. Doing that by hand means changes across CI configuration, namespaces, deploy files, stores, and demo data, in an order that is easy to get wrong. The &lt;code&gt;project-starter-wizard&lt;/code&gt; skill handles it for you: one developer interview, then a single orchestrated run that ends in a booted shop, verified store by store.&lt;/p&gt;
&lt;h2 id=&quot;what-the-skill-does&quot;&gt;What the skill does&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-dev/setup.png&quot; alt=&quot;starter&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;project-starter-wizard&lt;/code&gt; turns a fresh, un-booted clone of the Spryker B2B Marketplace demoshop into your customer project. It collects everything once up front — in a developer interview, or from a setup questionnaire you fill instead. Either way, the answers land in a resumable state file, and the wizard then drives nine specialist skills in the one order that works.&lt;/p&gt;
&lt;p&gt;The wizard owns the conversation and the flow — it writes almost nothing itself. Each step delegates to a sibling skill that does the transformation work.&lt;/p&gt;
&lt;p&gt;It is also the resume entry point — if a previous run left a state file, invoking the skill again skips the interview and continues from the first unfinished step.&lt;/p&gt;
&lt;h2 id=&quot;before-you-start&quot;&gt;Before you start&lt;/h2&gt;
&lt;p&gt;The wizard needs the SDK’s skills loaded into your AI tool. If you have not done that yet, &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-installation.html&quot;&gt;install the AI Dev SDK&lt;/a&gt; first.&lt;/p&gt;
&lt;p&gt;For this wizard, use the Claude Code plugin: it installs into Claude Code itself and needs nothing from the project, so your tooling is ready on a fresh clone. The &lt;code&gt;ai-dev:setup&lt;/code&gt; console command is the alternative for other assistants, but it requires an installed project — which a fresh clone is not yet.&lt;/p&gt;
&lt;p&gt;The wizard requires plugin version 0.4.0 or later. If the plugin is already installed, run &lt;code&gt;/plugin&lt;/code&gt; and update it.&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;Verify the installation&lt;/div&gt;
&lt;p&gt;Type &lt;code&gt;/spryker-ai-dev-sdk:project-starter-wizard&lt;/code&gt; in Claude Code. The command autocompletes when the plugin is loaded; if it does not appear, run &lt;code&gt;/reload-plugins&lt;/code&gt; and check the plugin with &lt;code&gt;/plugin&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;h2 id=&quot;choose-a-demo-data-strategy&quot;&gt;Choose a demo-data strategy&lt;/h2&gt;
&lt;p&gt;Before starting the wizard, think about which strategy best fits your goal — the choice determines what you need to prepare in advance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Generate demo data.&lt;/strong&gt; Prepare product images and a logo before you start; this is optional, but highly recommended because the wizard can use the logo colors in the project theme.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adapt the existing demo data&lt;/strong&gt; to the new store structure. For example, existing products receive new prices and store relations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clean the demo data&lt;/strong&gt; so that everything is removed except the minimum required for the project setup.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The wizard does everything itself, except one thing: in generate mode, you supply the images and the logo — the wizard never fabricates them.&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;Time and tokens&lt;/div&gt;
&lt;p&gt;Adapt and clean are the smoothest options: one boot and a handful of confirmations. Data generation is the most resource-intensive option and requires a significant number of tokens. Real localization — including glossary and catalog translations rather than English stubs — is also resource-intensive and time-consuming. These options are worthwhile when needed, but choose them deliberately.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;h2 id=&quot;start-the-wizard&quot;&gt;Start the wizard&lt;/h2&gt;
&lt;p&gt;Clone the &lt;a href=&quot;/docs/about/all/spryker-marketplace/marketplace-b2b-suite.html&quot;&gt;Marketplace B2B Demo Shop&lt;/a&gt; and start Claude Code in the clone, without booting or installing the project first:&lt;/p&gt;
&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/spryker-shop/b2b-demo-marketplace.git my-project
cd my-project
claude
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Then describe your project. For example:&lt;/p&gt;
&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;We need to set up a project.
Ostrem sells marine hardware to boatyards and refit shops: brass compasses, brass handrails, brass cleats and ropes.
The logo is supplied as SVG in more than one color, with several variants: an icon, a horizontal lock-up with wordmark, a dark app tile, and a simplified favicon.
The images and logo are inside the ostrem-images folder.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;A shorter prompt such as &lt;em&gt;“We need to set up a project.”&lt;/em&gt; is also sufficient — you can provide the remaining details during the interview.&lt;/p&gt;
&lt;h2 id=&quot;the-interview&quot;&gt;The interview&lt;/h2&gt;
&lt;p&gt;The wizard asks nine short questions and needs no further configuration decisions after them. Each question shows the demo’s current default; accept it with one click or override it. Accepting every default is valid.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Project identity&lt;/strong&gt; — project name, local development domain, one or two brand colors, and an optional logo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Code area (namespace)&lt;/strong&gt; — keep the demo’s &lt;code&gt;Pyz&lt;/code&gt; namespace or choose your own private area to keep your code safe from Spryker updates. The wizard proposes a valid name.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Services and applications&lt;/strong&gt; — everything is enabled by default. Choose what to turn off or replace, including headless mode, the Merchant Portal, Storybook, the Storefront API, and the Backend API. Keep the infrastructure defaults unless your hosting requires otherwise.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stores and region&lt;/strong&gt; — describe your stores, including languages, currencies, countries, and timezone. The wizard proposes one region for confirmation; a multi-region setup requires manual follow-up.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Demo-data strategy&lt;/strong&gt; — confirm the strategy you chose earlier. Adapt asks for a currency exchange-rate table, clean asks nothing extra, and generate asks for your theme, products, categories, prices, image folders, and content language.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Catalog scope&lt;/strong&gt; (adapt only) — choose the entire demo catalog or a subset.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Localization&lt;/strong&gt; — off by default, with each language starting as an English copy. Enable it only when you need real translation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic quality checks (CI)&lt;/strong&gt; — set up a lightweight check for every change, skip it, or let a developer tune it later.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run mode&lt;/strong&gt; — choose autonomous or supervised; see &lt;a href=&quot;#run-modes&quot;&gt;Run modes&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The wizard summarizes the configuration and asks for confirmation before making any changes. After you confirm, the setup runs without further configuration questions.&lt;/p&gt;
&lt;h2 id=&quot;the-nine-steps&quot;&gt;The nine steps&lt;/h2&gt;
&lt;p&gt;Steps 1 to 7 run before the first boot, because each one changes files the boot reads.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;project-ci-generator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rebuilds the inherited product CI into a single lean project pipeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;configure-codebase&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Registers the custom namespace and wires autoload, frontend build, and Codeception. Skipped when the project keeps &lt;code&gt;Pyz&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;brand-project&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Applies the project identity — name, domain, Docker namespace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;configure-services&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Writes the chosen engines, development services, and applications into &lt;code&gt;deploy.dev.yml&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;define-stores&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creates the region and store definitions. Skipped when the data mode is &lt;code&gt;leave&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;code&gt;project-data&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Adapts, cleans, generates, or leaves the import data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cypress-migration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Replaces the demoshop test suites with a project-owned Cypress baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boot-and-verify&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;First boot and per-store verification, including applying the theme&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;code&gt;translate-content&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Translates storefront content. Runs only when you selected locales to localize&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;run-modes&quot;&gt;Run modes&lt;/h2&gt;
&lt;p&gt;You choose the run mode in the interview, and the wizard honors it again on resume:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Autonomous&lt;/strong&gt; — steps 1 to 9 run as one continuous pass. At a reversible decision point the wizard picks the best option and records it in a decision log.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Supervised&lt;/strong&gt; — the wizard reports a one-line result and asks &lt;em&gt;“Continue to the next step?”&lt;/em&gt; at each step boundary. A good choice for your first run.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;when-the-wizard-stops-for-you&quot;&gt;When the wizard stops for you&lt;/h2&gt;
&lt;p&gt;Neither run mode relaxes the hard stops. During the run, the wizard may print &lt;code&gt;⚠ ACTION NEEDED&lt;/code&gt; and wait for something only you can do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Start Docker or OrbStack&lt;/strong&gt; — the wizard cannot start it for you.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Update &lt;code&gt;/etc/hosts&lt;/code&gt;&lt;/strong&gt; — this requires sudo. If you skip it, the setup still completes, but browser checks are marked as blocked.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Approve destructive actions&lt;/strong&gt; — the wizard shows exactly what will be removed and waits for your confirmation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Supply your own images and logo&lt;/strong&gt; in generate mode.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Point Git at your repository and commit when ready&lt;/strong&gt; — the wizard stages files but never commits or pushes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Any step failure also returns control to you, in both modes.&lt;/p&gt;
&lt;h2 id=&quot;run-artifacts&quot;&gt;Run artifacts&lt;/h2&gt;
&lt;p&gt;The run writes three files into the clone’s own tree under &lt;code&gt;.ai-dev/&lt;/code&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;project-setup.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The state — interview answers and a step table with per-step status. Resume reads this file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;run.log&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The timeline — step boundaries, conditional skips with their reason, hard stops, and every resume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;decision-log.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The rationale — each autonomous decision with its evidence and how to reverse it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;what-you-get-at-the-end&quot;&gt;What you get at the end&lt;/h2&gt;
&lt;p&gt;A transformed clone with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The project identity and branding applied&lt;/li&gt;
&lt;li&gt;A registered custom namespace&lt;/li&gt;
&lt;li&gt;The services and stores you chose&lt;/li&gt;
&lt;li&gt;Project-shaped import data&lt;/li&gt;
&lt;li&gt;A lean, project-owned CI pipeline&lt;/li&gt;
&lt;li&gt;A vendored Cypress test suite&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of it booted and verified per store by an independent verifier agent, with all changes staged but never committed — you review everything before it enters the project’s history.&lt;/p&gt;
&lt;p&gt;The closing summary flags what still needs a human before go-live, such as a git remote that still points at the demoshop upstream, a still-shipped Spryker logo, or translation debt from locales left as English copies.&lt;/p&gt;
&lt;h2 id=&quot;troubleshooting&quot;&gt;Troubleshooting&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The wizard has stopped and appears to be waiting.&lt;/strong&gt; Look for a line beginning &lt;code&gt;⚠ ACTION NEEDED&lt;/code&gt;. The wizard is waiting for an action only you can perform, such as starting Docker or OrbStack, adding the &lt;code&gt;/etc/hosts&lt;/code&gt; entry, or approving a destructive action. Complete the requested action and the wizard continues.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The boot fails or the shop does not start.&lt;/strong&gt; Check that Docker or OrbStack is running, ports 80 and 443 are available, and your disk has enough free space. A GitHub rate limit may also have interrupted the installation. Follow the wizard’s message, fix the issue on your machine, and ask Claude Code to retry. Do not edit project files to force the boot; the wizard is designed to handle this.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data from a previous project appears, or a name or volume collision is reported.&lt;/strong&gt; Another project on your machine may already use the same name. Choose a different project name, or remove the previous project’s leftover Docker volumes using the safe options provided by the wizard.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The storefront does not open in the browser, although the checks passed.&lt;/strong&gt; The &lt;code&gt;/etc/hosts&lt;/code&gt; entry is probably missing. Add the exact entry provided by the wizard, or accept the browser-check limitation. The setup itself has still completed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I selected the wrong stores or demo-data strategy.&lt;/strong&gt; Before the boot starts, tell Claude Code what to change. On a fresh clone, the wizard can undo and rerun the configuration from the appropriate point. After the boot, changing stores or the catalog requires the data to be wiped and reloaded; the wizard explains what will be removed before asking for approval.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The session closed, or I interrupted the run.&lt;/strong&gt; Reopen Claude Code in the same project directory and say &lt;em&gt;“Continue the project setup.”&lt;/em&gt; The wizard reads the completed work and resumes from where it stopped; it does not start over.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The setup is slow or uses many tokens.&lt;/strong&gt; This is expected in generate mode or with real translation enabled. If you do not need these options, adapt or clean is considerably lighter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The wizard will not start: “not a fresh clone” or “wrong shop”.&lt;/strong&gt; It runs only on an untouched, un-booted B2B Marketplace demoshop. Start with a clean clone, or ask Claude Code to guide you through restoring the project to a fresh state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A step has failed.&lt;/strong&gt; The wizard stops at the failed step and records what happened; it never continues past a failure. Share the exact message with Claude Code so it can check the known issues before attempting a fix.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;requirements&quot;&gt;Requirements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A fresh, un-booted clone of the &lt;a href=&quot;/docs/about/all/spryker-marketplace/marketplace-b2b-suite.html&quot;&gt;Spryker B2B Marketplace demoshop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;An AI tool with the SDK’s skills loaded — see &lt;a href=&quot;#before-you-start&quot;&gt;Before you start&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related&quot;&gt;Related&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-ai-dev-sdk/skills/project-starter-wizard/README.md&quot;&gt;&lt;code&gt;project-starter-wizard&lt;/code&gt; README&lt;/a&gt; — the skill’s own reference in the plugin repository&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-workflows-skills-and-agents.html&quot;&gt;Workflows, Skills, and Agents&lt;/a&gt; — the full reference of every skill and agent this wizard composes&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-customization-workflow.html&quot;&gt;Customization Workflow&lt;/a&gt; — build features on the project once it runs&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-installation.html&quot;&gt;Installation&lt;/a&gt; — install the SDK and generate your project’s rules, context file, and skills&lt;/li&gt;
&lt;/ul&gt;
</description>
            <pubDate>Mon, 24 Aug 2026 13:00:28 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/ai/ai-dev/ai-dev-project-starter-wizard.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/ai/ai-dev/ai-dev-project-starter-wizard.html</guid>
            
            
        </item>
        
        <item>
            <title>Profiler Workflow</title>
            <description>&lt;h2 id=&quot;availability&quot;&gt;Availability&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;spryker-profiler&lt;/code&gt; skill is available from &lt;code&gt;spryker-sdk/ai-dev&lt;/code&gt; version 0.6.4, which ships version 0.4.0 of the &lt;code&gt;spryker-ai-dev-sdk&lt;/code&gt; Claude Code plugin.&lt;/p&gt;
&lt;p&gt;To update the Claude Code plugin, run &lt;code&gt;/plugin&lt;/code&gt; in Claude Code and update &lt;code&gt;spryker-ai-dev-sdk&lt;/code&gt; from the &lt;code&gt;spryker-plugins-official&lt;/code&gt; marketplace to version 0.4.0 or later. For installation instructions, see &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-claude-code.html&quot;&gt;Claude Code&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-the-skill-does&quot;&gt;What the skill does&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-dev/profiler.png&quot; alt=&quot;profiler&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;spryker-profiler&lt;/code&gt; turns the Spryker and Symfony WebProfiler into performance numbers you can act on, and explains how to switch profiling on when there is nothing to read.&lt;/p&gt;
&lt;p&gt;Spryker records every web request automatically. That history is the cheapest source of truth for performance work: instead of guessing which code is slow, the skill reads what actually ran. It reduces a stored profile to the metrics that matter and prints JSON, so nobody has to scrape the profiler’s HTML or open a browser.&lt;/p&gt;
&lt;p&gt;Invoke it when you ask why a page, endpoint, or Back Office screen is slow, want the heaviest request, suspect an N+1 query, need before-and-after evidence that a performance fix worked, or want to know what a request logged, threw, or which controller handled it. It also covers profiler setup — a missing toolbar, a collector showing no data, or enabling profiling for a specific application.&lt;/p&gt;
&lt;h2 id=&quot;what-it-can-tell-you&quot;&gt;What it can tell you&lt;/h2&gt;
&lt;p&gt;The I/O counters say how much work a request did:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Answers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;queries&lt;/code&gt; / &lt;code&gt;unique&lt;/code&gt; / &lt;code&gt;duplicates&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How many SQL statements a request ran, and how many were repeats&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;redis&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Key-value operations per request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;elasticsearch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Search calls per request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zed_requests&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yves-to-Zed calls — the architecture boundary check&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;external_http&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Third-party calls blocking the response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;duration_ms&lt;/code&gt; / &lt;code&gt;memory_mb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Wall time and peak memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;segments&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Queries attributed to a named code path you wrapped&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;These say why, and whether the numbers are comparable between runs:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Answers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;logs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Errors, warnings, and deprecations logged during the request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;audit_log&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Security and compliance events by channel — a separate stream from &lt;code&gt;logs&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;exception&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;What was thrown, with message and status code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;twig&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Template count and render time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;events&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Listeners called synchronously, plus Spryker application events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;http&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which controller and route handled the request, and where a 3xx redirected to&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;session&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Session payload size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;runtime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Debug mode and Xdebug flags — check these before comparing any timing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A Spryker request records 20 collectors. The skill reads every one that carries request data and maps it onto the fields above. The three it does not read hold no per-request measurements — they are toolbar plumbing and a dump of resolved configuration.&lt;/p&gt;
&lt;h2 id=&quot;one-user-action-is-many-profiles&quot;&gt;One user action is many profiles&lt;/h2&gt;
&lt;p&gt;The mistake that most often produces a wrong answer is treating a profile as a user action. A profile is one HTTP request. Clicking &lt;strong&gt;Login&lt;/strong&gt; on the storefront creates six profiles across two applications and two storage directories — and the storefront profile itself reports zero queries, because Yves has no SQL collector by design.&lt;/p&gt;
&lt;p&gt;The skill reconstructs the full tree instead. Yves-to-Zed links are exact, because the callee returns its own debug token, which the caller stores. Browser-issued AJAX and ESI sub-requests carry no such link, so those are grouped by time window and flagged as the heuristic they are.&lt;/p&gt;
&lt;h2 id=&quot;the-workflow&quot;&gt;The workflow&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;If you do not know which page is slow, rank the recorded profiles by a metric to find the outlier.&lt;/li&gt;
&lt;li&gt;Reproduce the request, then read the profile and check that its age is seconds — profiles accumulate for days, and without checking you may analyze last week’s request.&lt;/li&gt;
&lt;li&gt;If a collector is absent or there are no profiles at all, the skill walks the setup layers that must be enabled.&lt;/li&gt;
&lt;li&gt;For a page or user action, read the full trace rather than the entry request alone.&lt;/li&gt;
&lt;li&gt;Read the counts, not the milliseconds. Local wall time swings widely for identical work, while I/O counts are stable and are what scales badly in production.&lt;/li&gt;
&lt;li&gt;If the counts are high, profile a small and a large entity to see whether the count scales with the data.&lt;/li&gt;
&lt;/ol&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;An absent collector is not zero&lt;/div&gt;
&lt;p&gt;&lt;code&gt;collector: &amp;quot;absent&amp;quot;&lt;/code&gt; means the metric was never measured for that application — Yves has no SQL collector, and the Back Office has no Redis collector. Reporting zero queries from an absent collector is a false conclusion. &lt;code&gt;collector: &amp;quot;incompatible&amp;quot;&lt;/code&gt; means the collector is recording but an upgrade renamed part of its API, so the data is still available in the browser panel.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;h2 id=&quot;going-deeper-than-counts&quot;&gt;Going deeper than counts&lt;/h2&gt;
&lt;p&gt;Segmented SQL attributes queries to a named code path, turning “this page runs 261 queries” into “the calculator stack runs 180 of them”. Wrap the suspect code and the reader reports it under &lt;code&gt;database.segments&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;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Shared\Propel\Logger\PropelInMemoryLogger&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nc&quot;&gt;PropelInMemoryLogger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;startSegment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;order-validation&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;p&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;validateOrder&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$orderTransfer&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;finally&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;PropelInMemoryLogger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;endSegment&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;Always use &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;finally&lt;/code&gt;. The logger is static, so an unclosed segment swallows every later query in the request. Remove segments once you have the answer — they are debugging scaffolding.&lt;/p&gt;
&lt;p&gt;A stored profile holds far more than the reader prints, including full SQL text, headers, routing, events, and logger entries. When a question needs something the reader does not expose, the skill extends the script rather than stopping at the available options.&lt;/p&gt;
&lt;h2 id=&quot;things-that-will-confuse-you&quot;&gt;Things that will confuse you&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Timings are only comparable at equal runtime.&lt;/strong&gt; Xdebug inflates wall time several-fold and debug mode disables caches, so a “regression” can be nothing more than a differently configured container.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Each application writes to its own directory.&lt;/strong&gt; Yves and Glue write to one directory; Zed, the Back Office, the Backend Gateway, and the Merchant Portal write to another. The reader picks the most recently written one, which is often not the one you want.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The index outlives the data.&lt;/strong&gt; Stored profiles are deleted after two days, but the index is never trimmed, so it can list thousands of requests when only a few dozen files survive. The skill reports how many profiles a ranking actually covered.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;external_http: 0&lt;/code&gt; may mean “not instrumented”.&lt;/strong&gt; External calls appear only if the calling code uses the external HTTP logger trait, so a zero is never proof that a request makes no outbound calls.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Login walls profile the redirect.&lt;/strong&gt; An unauthenticated request to the Back Office or Merchant Portal records the redirect to the login form, which says nothing about the page you wanted. Reproduce with an authenticated session.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Back Office tables are two requests.&lt;/strong&gt; The page renders an empty grid and loads its rows through a separate table route, which is usually where the real cost sits. Profile both.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Console commands, queue workers, and cron jobs are never profiled.&lt;/strong&gt; The WebProfiler is request-scoped. Use Xdebug profiling or explicit timing for those.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;when-there-is-no-data&quot;&gt;When there is no data&lt;/h2&gt;
&lt;p&gt;Three independent layers must all be in place, and the skill checks each one:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The web profiler is enabled in the project configuration.&lt;/li&gt;
&lt;li&gt;The web profiler application plugin is registered for the application you are profiling. Zed alone has four separate stacks — Zed, Back Office, Backend Gateway, and Backend API.&lt;/li&gt;
&lt;li&gt;A collector plugin for the metric you want is registered in that application’s &lt;code&gt;WebProfilerDependencyProvider&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After changing any of these, empty the cache and reproduce the request.&lt;/p&gt;
&lt;h2 id=&quot;requirements&quot;&gt;Requirements&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A running Spryker project (Docker SDK up) with the web profiler enabled&lt;/li&gt;
&lt;li&gt;An AI tool with the SDK’s skills loaded — either through the &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-claude-code.html&quot;&gt;Claude Code plugin&lt;/a&gt; or via &lt;code&gt;ai-dev:setup&lt;/code&gt; for another supported tool&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;related&quot;&gt;Related&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/integrate-and-configure/integrate-development-tools/web-profiler.html&quot;&gt;WebProfiler&lt;/a&gt; — why the profiler matters and how to integrate it per application&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/project-setup-wizard/plugins/spryker-ai-dev-sdk/skills/spryker-profiler/README.md&quot;&gt;&lt;code&gt;spryker-profiler&lt;/code&gt; README&lt;/a&gt; — the skill’s own reference in the plugin repository&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-workflows-skills-and-agents.html&quot;&gt;Workflows, Skills, and Agents&lt;/a&gt; — the full reference of every skill and agent&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-installation.html&quot;&gt;Installation&lt;/a&gt; — install the SDK and generate your project’s rules, context file, and skills&lt;/li&gt;
&lt;/ul&gt;
</description>
            <pubDate>Mon, 24 Aug 2026 13:00:28 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/ai/ai-dev/ai-dev-profiler-workflow.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/ai/ai-dev/ai-dev-profiler-workflow.html</guid>
            
            
        </item>
        
        <item>
            <title>AI Dev MCP Server</title>
            <description>&lt;p&gt;The AiDev module ships an MCP server that gives AI assistants live access to your running Spryker application: transfer objects, module interfaces, order management system (OMS) state machines, CSV data, and read-only database queries. This page describes what the server exposes, how to extend it, and how to debug it.&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;Install first&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;ai-dev:mcp-server&lt;/code&gt; command exists only after the &lt;code&gt;spryker-sdk/ai-dev&lt;/code&gt; module is installed in your project and its console commands are registered. To install the module and register the server in your assistant, see &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-installation.html&quot;&gt;Installation&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;h2 id=&quot;about-model-context-protocol-mcp&quot;&gt;About Model Context Protocol (MCP)&lt;/h2&gt;
&lt;p&gt;The Model Context Protocol (MCP) is an open-source standard for connecting AI applications to external systems.
For Spryker developers, MCP allows AI assistants like Claude or Copilot to understand your project, improving the quality of AI-generated code and recommendations.&lt;/p&gt;
&lt;p&gt;Learn more about MCP at &lt;a href=&quot;https://modelcontextprotocol.io/docs/getting-started/intro&quot;&gt;modelcontextprotocol.io&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;how-the-server-runs&quot;&gt;How the server runs&lt;/h2&gt;
&lt;p&gt;The module provides the server through the &lt;code&gt;ai-dev:mcp-server&lt;/code&gt; console command, which runs inside your project’s Docker container over the MCP stdio transport:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker/sdk console ai-dev:mcp-server &lt;span class=&quot;nt&quot;&gt;-q&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The command starts the server, registers all configured MCP tool plugins, and listens for requests from AI assistants. You do not usually run it by hand — your assistant starts it. For the registration steps per assistant, see &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-installation.html#register-the-mcp-server&quot;&gt;Register the MCP server&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;available-mcp-tools&quot;&gt;Available MCP tools&lt;/h2&gt;
&lt;p&gt;The AiDev module provides the following built-in tools that AI assistants can use:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool name&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getTransferStructureByName&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retrieves the structure of a Spryker transfer object by its name. Returns all properties with their types and metadata.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getTransferStructureByNamespace&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retrieves the structure of a Spryker transfer object by its fully qualified namespace.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getInterfaceMethodsByNamespace&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retrieves all method signatures, parameters, return types, and PHPDoc for a given interface FQN (Fully Qualified Name).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getOmsTransitionsByState&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retrieves OMS state machine transitions for a specific state. Returns all transitions that start from the given state, optionally filtered by process name.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getOrderOmsTransitions&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retrieves OMS state machine transitions for a specified order from the order’s current state. Helps identify the current state and possible transitions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;executeQuery&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Executes read-only database queries (SELECT, SHOW, DESCRIBE, EXPLAIN) for accessing project data without modification capabilities.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getSprykerModules&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lists all available Spryker modules from project and vendor directories.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;getSprykerModuleMap&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Retrieves detailed module metadata including class paths, method signatures, and extension points.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;searchAlgoliaDocumentation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enables keyword-based Spryker documentation search through Algolia integration.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;analyzeCsvFile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Analyzes CSV file structure without loading full content. Returns headers, row count, and sample rows. Supports optional column analysis with unique values and null counts.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transformCsv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transforms and modifies CSV files with three operation modes: APPEND (add new rows), REPLACE (overwrite target), and UPDATE (modify existing rows in-place). Supports column mappings, row filters, value transformations, default values, and automatic backup creation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deleteCsvRows&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deletes rows from CSV files based on filter criteria with multiple operators (equals, not_equals, in, not_in, contains, not_contains, starts_with, ends_with, empty, not_empty). Includes safety checks and automatic backup creation.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;splitOdsToCsv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Splits ODS (OpenDocument Spreadsheet) files into separate CSV files per sheet. Skips empty sheets and returns details about created files. Useful for converting Google Sheets exports to Spryker-compatible CSVs.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;AI assistants can automatically discover and use these tools when connected to the MCP server.&lt;/p&gt;
&lt;h2 id=&quot;extension-points&quot;&gt;Extension points&lt;/h2&gt;
&lt;p&gt;The AiDev module provides plugin interfaces for extending the MCP server with custom functionality.&lt;/p&gt;
&lt;h3 id=&quot;aidevmcptoolplugininterface&quot;&gt;AiDevMcpToolPluginInterface&lt;/h3&gt;
&lt;p&gt;Implement this interface to add custom MCP tools that AI assistants can use to query or interact with your application.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Interface location&lt;/strong&gt;: &lt;code&gt;SprykerSdk\Zed\AiDev\Dependency\AiDevMcpToolPluginInterface&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Integration&lt;/strong&gt;: Register your tool plugins in &lt;code&gt;AiDevDependencyProvider::getMcpToolPlugins()&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;Pyz\Zed\AiDev&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;SprykerSdk\Zed\AiDev\AiDevDependencyProvider&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SprykerAiDevDependencyProvider&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;Pyz\Zed\AiDev\Communication\Plugins\CustomAiDevMcpToolPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AiDevDependencyProvider&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SprykerAiDevDependencyProvider&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cd&quot;&gt;/**
     * @return array&amp;lt;\SprykerSdk\Zed\AiDev\Dependency\AiDevMcpToolPluginInterface&amp;gt;
     */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getMcpToolPlugins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;array&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;array_merge&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;parent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getMcpToolPlugins&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;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CustomAiDevMcpToolPlugin&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;h2 id=&quot;configuration&quot;&gt;Configuration&lt;/h2&gt;
&lt;p&gt;You can configure the AiDev module through the &lt;code&gt;AiDevConfig&lt;/code&gt; class. Refer to the module’s configuration class for the available options and their default values.&lt;/p&gt;
&lt;h2 id=&quot;debug-the-mcp-server&quot;&gt;Debug the MCP server&lt;/h2&gt;
&lt;p&gt;Before you connect the MCP server to an AI assistant, you can test and debug it with the &lt;a href=&quot;https://modelcontextprotocol.io/docs/tools/inspector&quot;&gt;MCP Inspector&lt;/a&gt;. The inspector provides a web interface to interact with your MCP server, test tools, and verify that everything works correctly.&lt;/p&gt;
&lt;p&gt;Go to your Spryker project directory and run:&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;npx @modelcontextprotocol/inspector docker/sdk console ai-dev:mcp-server &lt;span class=&quot;nt&quot;&gt;-q&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This command:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Starts the MCP Inspector in your browser&lt;/li&gt;
&lt;li&gt;Connects to your local MCP server&lt;/li&gt;
&lt;li&gt;Displays all available tools&lt;/li&gt;
&lt;li&gt;Lets you test tool calls interactively&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To run the inspector with Xdebug:&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;npx @modelcontextprotocol/inspector docker/sdk cli &lt;span class=&quot;nt&quot;&gt;-x&lt;/span&gt; console ai-dev:mcp-server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&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;Node.js required&lt;/div&gt;
&lt;p&gt;The MCP Inspector requires Node.js on your system. The &lt;code&gt;npx&lt;/code&gt; command automatically downloads and runs the inspector without a global installation.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-dev/mcp-inspector.png&quot; alt=&quot;MCP Inspector listing the Spryker MCP tools&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;related&quot;&gt;Related&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-installation.html&quot;&gt;Installation&lt;/a&gt; — install the module and register the server in your assistant&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev.html&quot;&gt;AI Dev SDK&lt;/a&gt; — what the SDK is and what it ships&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-workflows-skills-and-agents.html&quot;&gt;Workflows, Skills, and Agents&lt;/a&gt; — the workflows that consume these tools&lt;/li&gt;
&lt;/ul&gt;
</description>
            <pubDate>Mon, 24 Aug 2026 13:00:28 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/ai/ai-dev/ai-dev-mcp-server.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/ai/ai-dev/ai-dev-mcp-server.html</guid>
            
            
        </item>
        
    </channel>
</rss>
