<?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>Wed, 28 Oct 2026 14:59:30 +0000</lastBuildDate>
        <generator>Jekyll v4.2.2</generator>
        
        
        <item>
            <title>Marketplace order invoice documents feature overview</title>
            <description>{% include pbc/all/order-management-system/order-invoice-liability.md %} &lt;!-- To edit, see /_includes/pbc/all/order-management-system/order-invoice-liability.md --&gt;

The *Marketplace order invoice documents* feature extends the [Order invoice documents](/docs/pbc/all/order-management-system/latest/base-shop/order-management-feature-overview/order-invoice-documents-overview.html) feature for marketplace shops. A marketplace order contains items of one or several merchants and, optionally, items the marketplace operator sells. Instead of one invoice for the whole order, the feature creates one invoice per merchant plus one invoice for the items without a merchant. Each invoice shows only the items, expenses, and totals of its merchant, together with the merchant&apos;s requisites, and merchants open their invoices in the Merchant Portal.

The feature is provided by the `MerchantSalesInvoice` module. The `SalesInvoice` module stays unaware of merchants: `MerchantSalesInvoice` plugs into it through the plugin interfaces of the `SalesInvoiceExtension` module and stores the merchant of an invoice in the new `merchant_reference` column of the `spy_sales_order_invoice` table. Shops without `MerchantSalesInvoice` keep one invoice per order.

## How it works

1. A merchant ships items in the Merchant Portal. The merchant state machine runs the `ship` event, which moves the items of the marketplace order into the `shipped by merchant` state of the marketplace state machine.
2. Entering `shipped by merchant` triggers the `invoice-generate` event with the `MerchantInvoice/Generate` OMS command (`GenerateMerchantOrderInvoicesCommandPlugin`). The command groups the items of the event by merchant reference and asks `SalesInvoice` to generate one invoice per group. Items without a merchant reference form the operator&apos;s group. The `deliver` event of the subprocess then moves the items from `invoice generated` to `delivered`, and the `MarketplaceReturn` subprocess accepts `start-return` from `invoice generated` as well as from `shipped by merchant` and `delivered`.
3. `MerchantReferenceOrderInvoiceBeforeSavePlugin` stores the merchant reference of the group in the invoice. `MerchantOrderInvoiceExistenceCheckStrategyPlugin` makes sure that an order gets at most one invoice per merchant: a unique key on the order and the merchant reference backs the check.
4. When the invoice is rendered, in the email, on the Storefront, in the Back Office, or in the Merchant Portal, `MerchantOrderInvoiceRenderOrderExpanderPlugin` replaces the order in the template with a copy that contains only the items and expenses of the invoice&apos;s merchant and the totals of the [merchant order](/docs/pbc/all/order-management-system/latest/marketplace/marketplace-order-management-feature-overview/merchant-order-overview.html). It also adds the merchant to the invoice, so the template prints the merchant&apos;s name, registration number, and address. For the operator&apos;s invoice, the copy keeps the items and expenses without a merchant and recalculates the totals.
5. `MerchantOrderInvoicesExpanderPlugin` adds the merchant to every loaded invoice, so the Back Office **Documents** block shows a **Merchant** column, and the Storefront row shows the merchant through the `SoldByMerchantWidget`.

### Invoice per merchant order

The invoice of a merchant is created on the merchant&apos;s first `shipped by merchant` transition and is rendered from the whole merchant order. If the merchant ships the items of one order in several shipments, the later shipments do not create another invoice: the invoice created with the first shipment already lists all items of the merchant in that order. This mirrors the base shop, where the whole order is invoiced at once. Items canceled after the invoice was created are not reflected; credit notes are not supported.

The `MerchantInvoice/Generate` command also runs the manual `invoice-generate` event of the demo `DummyPayment01` process, so a Back Office user who triggers it on a marketplace order with the regular invoice payment method gets the same set of invoices: one per merchant plus one for the operator&apos;s items.

## Merchant Portal

The order details page of the Merchant Portal shows a **Documents** card with the invoices issued for the merchant&apos;s part of the order: the reference, the issue date, and a **PDF** link. The link opens the invoice page in a new tab, in the Merchant Portal layout, with a **Save as PDF** button and the document in a frame. The page answers with a 404 page unless the merchant order belongs to the merchant of the logged in merchant user and the invoice belongs to that merchant order&apos;s merchant. Merchant users receive access to the page through the `merchant-sales-invoice` ACL bundle, which `MerchantSalesInvoiceMerchantAclRuleExpanderPlugin` adds to every merchant. Because the invoice is generated inside the Merchant Portal request that ships the items, `MerchantSalesInvoiceMerchantAclEntityRuleExpanderPlugin` grants merchant users the ACL entity rule to create, read, and update the invoices of their orders, and `MerchantSalesInvoiceAclEntityConfigurationExpanderPlugin` registers the invoice as a child of the order and allows the sequence number entity, so that the invoice reference can be generated. For details, see [View merchant order invoice documents in the Merchant Portal](/docs/pbc/all/order-management-system/latest/marketplace/view-merchant-order-invoice-documents.html).

## Back Office

The **Documents** block of the order page lists every invoice of the order with its reference, date, merchant, and the **PDF** link. `MerchantOrderInvoiceSalesListBlockRendererPlugin` of `MerchantSalesInvoice` renders the block with the **Merchant** column and replaces the block plugin of `SalesInvoice`. The operator&apos;s invoice shows an empty **Merchant** cell. For details, see [View order invoice documents](/docs/pbc/all/order-management-system/latest/base-shop/manage-in-the-back-office/orders/view-order-invoice-documents.html).

## Storefront

The **Documents** section of the order details page lists every invoice of the order. Each row shows the reference, the issue date, the merchant through the `SoldByMerchantWidget` of the `MerchantWidget` module, and the **PDF** link. Access follows the rules of the order details page; see [Order invoice documents feature overview](/docs/pbc/all/order-management-system/latest/base-shop/order-management-feature-overview/order-invoice-documents-overview.html#storefront).

## The document

Each invoice renders the invoice template of your project. The template receives the narrowed order and the invoice with the merchant, so it can print the merchant&apos;s requisites next to the operator&apos;s header; the demo template prints the merchant&apos;s name, registration number, and the address from the merchant profile. For the template and the extension points, see [Customize the order invoice document](/docs/pbc/all/order-management-system/latest/base-shop/customize-the-order-invoice-document.html).

## Current constraints

- One invoice per merchant order, created with the merchant&apos;s first shipment, plus one invoice for the items without a merchant. Shipments and partial deliveries do not get separate invoices.
- Invoice references stay in one sequence for all merchants, for example `Invoice-12`, `Invoice-13`.
- Every invoice is sent as its own invoice email, so the customer gets one email per merchant and one for the items without a merchant. The BCC recipients configured in `SalesInvoiceConfig::getOrderInvoiceBcc()` receive each email too; merchants receive no email.
- Credit notes and delivery notes are not supported.
- The unique key on the order and the merchant reference does not cover the operator&apos;s invoice, whose merchant reference is `NULL`, which matches the base shop behavior for all invoices.
</description>
            <pubDate>Tue, 27 Oct 2026 10:49:25 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/order-management-system/latest/marketplace/marketplace-order-management-feature-overview/marketplace-order-invoice-documents-overview.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/order-management-system/latest/marketplace/marketplace-order-management-feature-overview/marketplace-order-invoice-documents-overview.html</guid>
            
            
        </item>
        
        <item>
            <title>Install the Order Management feature</title>
            <description>{% include pbc/all/install-features/latest/install-the-order-management-feature.md %} &lt;!-- To edit, see /_includes/pbc/all/install-features/latest/install-the-order-management-feature.md --&gt;</description>
            <pubDate>Tue, 27 Oct 2026 10:49:25 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/order-management-system/latest/base-shop/install-and-upgrade/install-features/install-the-order-management-feature.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/order-management-system/latest/base-shop/install-and-upgrade/install-features/install-the-order-management-feature.html</guid>
            
            
        </item>
        
        <item>
            <title>Install the Marketplace Order Invoice Documents feature</title>
            <description>{% include pbc/all/install-features/latest/marketplace/install-the-marketplace-order-invoice-documents-feature.md %} &lt;!-- To edit, see /_includes/pbc/all/install-features/latest/marketplace/install-the-marketplace-order-invoice-documents-feature.md --&gt;
</description>
            <pubDate>Tue, 27 Oct 2026 10:49:25 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/order-management-system/latest/marketplace/install-features/install-the-marketplace-order-invoice-documents-feature.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/order-management-system/latest/marketplace/install-features/install-the-marketplace-order-invoice-documents-feature.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 |
| PurchasingControlAclEntityConfigurationExpanderPlugin | Grants global read, update, and delete access to `SpyBudgetConsumption` in the Merchant Portal ACL configuration. This lets the budget restore OMS commands adjust budget consumption when a merchant user triggers them. Required only for marketplace projects with the Merchant Portal. | None | SprykerFeature\Zed\PurchasingControl\Communication\Plugin\AclMerchantPortal |
| 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;
```

#### Grant Merchant Portal access to budget consumption

In a marketplace, merchant users can cancel and refund order items from the Merchant Portal. The Merchant Portal applies ACL entity rules to every database query, so without explicit access, the `CostCenter/RestoreBudgetOnCancel` and `CostCenter/RestoreBudgetOnRefund` commands cannot update `spy_budget_consumption` and the budget is not restored. If your project does not use the Merchant Portal, skip this step.

**src/Pyz/Zed/AclMerchantPortal/AclMerchantPortalDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Zed\AclMerchantPortal;

use Spryker\Zed\AclMerchantPortal\AclMerchantPortalDependencyProvider as SprykerAclMerchantPortalDependencyProvider;
use SprykerFeature\Zed\PurchasingControl\Communication\Plugin\AclMerchantPortal\PurchasingControlAclEntityConfigurationExpanderPlugin;

class AclMerchantPortalDependencyProvider extends SprykerAclMerchantPortalDependencyProvider
{
    /**
     * @return list&lt;\Spryker\Zed\AclMerchantPortalExtension\Dependency\Plugin\AclEntityConfigurationExpanderPluginInterface&gt;
     */
    protected function getAclEntityConfigurationExpanderPlugins(): array
    {
        return [
            // ...
            new PurchasingControlAclEntityConfigurationExpanderPlugin(), #PurchasingControlFeature
        ];
    }
}
```

#### 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.
- Marketplace only: In the Merchant Portal, cancel or refund an item of an order placed with a budget. Make sure the budget balance is restored and the item moves to the `refunded` state after a refund.

{% 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, 27 Oct 2026 08:19:49 +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 Workflows feature</title>
            <description>This document describes how to install the Workflows feature.

## Prerequisites

Install the required modules:

| MODULE | VERSION |
|--------|---------|
| spryker/workflow | ^0.4.0 |
| spryker/state-machine | ^2.26.0 |
| spryker/data-import | ^1.27.1 |
| spryker/gui | ^5.11.0 |
| spryker/kernel | ^3.84.0 |
| spryker/user | ^3.17.0 |

## Install feature core

### 1) Install the required modules

Install the Workflow module and update its dependencies to the required versions:

```bash
composer require spryker/workflow:&quot;^0.4.0&quot; --update-with-dependencies
```

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

Make sure the following modules are available in `vendor/spryker/`:

| MODULE |
|--------|
| spryker/workflow |
| spryker/state-machine |
| spryker/data-import |
| spryker/gui |
| spryker/user |

{% endinfo_block %}

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

Apply the database changes and generate the transfer objects:

```bash
docker/sdk cli console propel:install
docker/sdk cli console transfer:generate
```

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

Make sure that the following tables exist in the database:

| DATABASE ENTITY | TYPE | EVENT |
|-----------------|------|-------|
| spy_state_machine_process | table | created |
| spy_state_machine_process_definition | table | created |
| spy_state_machine_process_definition_instance | table | created |
| spy_state_machine_process_definition_trigger | table | created |

{% endinfo_block %}

### 3) Configure navigation

The Workflow Back Office menu entry is added under **Administration**. Add the following node to `config/Zed/navigation.xml`:

```xml
&lt;workflows&gt;
    &lt;label&gt;Workflows&lt;/label&gt;
    &lt;title&gt;Workflows&lt;/title&gt;
    &lt;bundle&gt;workflow&lt;/bundle&gt;
    &lt;controller&gt;process&lt;/controller&gt;
    &lt;action&gt;index&lt;/action&gt;
&lt;/workflows&gt;
```

Rebuild the navigation cache:

```bash
docker/sdk cli console navigation:build-cache
docker/sdk cli console cache:empty-all
```

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

In the Back Office, go to **Administration &gt; Workflows** and make sure the page opens.

{% endinfo_block %}

### 4) Build the Back Office assets and translations

The workflow builder and the action confirmation modal are part of the Back Office frontend assets, and the Workflow and Gui modules ship Back Office translations. Build the assets and generate the translation cache:

```bash
docker/sdk cli console frontend:zed:build
docker/sdk cli console translator:generate-cache
```

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

In the Back Office, go to **Administration &gt; Workflows** and click **Create Workflow**. Make sure the visual builder is displayed and the page labels are translated.

{% endinfo_block %}

### 5) Create a workflow in the Back Office

Once the feature is installed, a Back Office user can author a workflow through the UI. The **Subject type** and **Triggers** fields list only the subject types and events provided by registered trigger plugins, so register at least one trigger plugin first. For details, see [Register command, condition, and trigger plugins](#register-command-condition-and-trigger-plugins).

1. Go to **Administration &gt; Workflows** and click **Create Workflow**.
2. Enter a **Name** and, optionally, a **Description**.
3. Select a **Subject type**, then select one or more **Triggers** of that subject type. Triggers apply to all versions of the workflow.
4. In **Version 1**, draw the states and transitions in the visual builder and mark the initial state. To check the definition before saving, click **Validate**.
5. Click **Create workflow**. The workflow and its first version are created inactive.
6. Activate the version and the workflow. Each activation asks for confirmation.

The workflow is now live: whenever a selected trigger event fires for a subject of the configured type, a new instance starts on the active version.

To change the description or triggers later, open the workflow and click **Edit description and triggers**. To change the definition, click **New version**, then **Save as new version**.

{% info_block infoBox &quot;Provisioning instead of manual authoring&quot; %}

To ship a ready-made workflow with your project instead of creating it by hand, use the data import in the following steps. The two approaches are interchangeable — both produce the same process, versions, and triggers.

{% endinfo_block %}

### 6) Register command, condition, and trigger plugins

Register your project&apos;s commands, conditions, and start triggers by extending the core `WorkflowDependencyProvider`.

**src/Pyz/Zed/Workflow/WorkflowDependencyProvider.php**

```php
&lt;?php

namespace Pyz\Zed\Workflow;

use Spryker\Zed\Workflow\WorkflowDependencyProvider as SprykerWorkflowDependencyProvider;

class WorkflowDependencyProvider extends SprykerWorkflowDependencyProvider
{
    /**
     * @return array&lt;\Spryker\Zed\Workflow\Dependency\Plugin\WorkflowCommandPluginInterface&gt;
     */
    protected function getCommandPlugins(): array
    {
        return [
            // new MyCommandPlugin(),
        ];
    }

    /**
     * @return array&lt;\Spryker\Zed\Workflow\Dependency\Plugin\WorkflowConditionPluginInterface&gt;
     */
    protected function getConditionPlugins(): array
    {
        return [
            // new MyConditionPlugin(),
        ];
    }

    /**
     * @return array&lt;\Spryker\Zed\Workflow\Dependency\Plugin\StateMachineProcessTriggerPluginInterface&gt;
     */
    protected function getTriggerPlugins(): array
    {
        return [
            // new MyProcessTriggerPlugin(),
        ];
    }
}
```

For the plugin interfaces and how the engine resolves them, see [Extending a workflow](/docs/pbc/all/back-office/latest/base-shop/workflows-feature-overview.html#extending-a-workflow).

A trigger plugin only makes an event selectable in the Back Office. To start an instance when the event fires, your project also needs an event listener. For a complete example, see [Add a subject type to the Workflows feature](/docs/dg/dev/integrate-and-configure/add-a-subject-type-to-the-workflows-feature.html).

### 7) Register the data importer

Register the Workflow data import plugin so workflows can be provisioned from CSV.

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

```php
use Spryker\Zed\Workflow\Communication\Plugin\DataImport\WorkflowDataImportPlugin;

/**
 * @return array&lt;\Spryker\Zed\DataImport\Dependency\Plugin\DataImportPluginInterface&gt;
 */
protected function getDataImporterPlugins(): array
{
    return [
        new WorkflowDataImportPlugin(),
    ];
}
```

### 8) Provide the import data

The import uses two files: a definition XML file that describes the state machine (its states, transitions, and events, in the same `state-machine-01` format used by the OMS), and a CSV that provisions the workflow and points at that XML file. Storing the definition in its own file keeps the CSV readable and lets you edit the state machine like any other XML process.

The `definition` column in the CSV holds the path to the XML file, relative to the project root.

**data/import/common/common/workflow/company_onboarding.xml**

```xml
{% raw %}&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;statemachine
    xmlns=&quot;spryker:state-machine-01&quot;
    xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
    xsi:schemaLocation=&quot;spryker:state-machine-01 http://static.spryker.com/state-machine-01.xsd&quot;
&gt;
    &lt;process name=&quot;CompanyOnboarding&quot; main=&quot;true&quot;&gt;
        &lt;!-- states, transitions, and events --&gt;
    &lt;/process&gt;
&lt;/statemachine&gt;{% endraw %}
```

**data/import/common/common/workflow.csv**

```csv
name,subject_type,description,initial_state,version,definition,trigger_events,is_active
CompanyOnboarding,Company,B2B company onboarding demo workflow,created,1,data/import/common/common/workflow/company_onboarding.xml,&quot;Entity.spy_company.create,Entity.spy_company.update&quot;,1
```

| COLUMN | REQUIRED | DESCRIPTION |
|--------|----------|-------------|
| name | yes | Workflow (process) name. |
| subject_type | yes | The subject the workflow applies to, for example `Company`. |
| description | no | Human-readable description. |
| initial_state | yes | The state a new instance starts in. |
| version | yes | Version number. The importer upserts on `(name, version)`, so re-imports do not create duplicates. |
| definition | yes | Path to the definition XML file, relative to the project root — for example `data/import/common/common/workflow/company_onboarding.xml`. |
| trigger_events | no | The Publish &amp; Synchronize event names custom type of triggers that start an instance, for example `Entity.spy_company.create`. To list several events, separate them with commas and wrap the whole cell in double quotes so the commas are not read as CSV column separators: `&quot;Entity.spy_company.create,Entity.spy_company.update&quot;`. |
| is_active | no | `1` activates this version and its process. |

Run the importer directly to verify:

```bash
docker/sdk cli console data:import workflow
```

### 9) Add the importer to the install recipe

Register the workflow importer in the data import configuration so it runs during deployment. Add it to the region import config that the install recipes invoke through `data:import`.

Add the following entry to the import config of **every region and environment you deploy** — the local files (`data/import/local/full_&lt;REGION&gt;.yml`) and the production files (`data/import/production/full_&lt;REGION&gt;.yml`). Adding it to only one file provisions the workflow only for that region and environment.

**data/import/local/full_EU.yml** (and the other `full_&lt;REGION&gt;.yml` files, local and production)

```yaml
    - data_entity: workflow
      source: data/import/common/common/workflow.csv
```

Place the entry after the modules whose subjects the workflow attaches to (for example `company`), so those subjects exist before the workflow is provisioned. The install recipes already call `data:import` with the region import config, so no recipe change is required beyond this entry.

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

Run the install recipe and make sure the workflow with its versions appears under **Administration &gt; Workflows**.

{% endinfo_block %}

### 10) Schedule the condition and timeout jobs

Condition and timeout transitions have no event, so they must be advanced by two console commands: `workflow:check-condition` and `workflow:check-timeout`. These commands are **not** scheduled out of the box — register them as recurring jobs the same way the OMS and state machine checks are scheduled. Add the following jobs to `config/Zed/cronjobs/jenkins.php`:

```php
$jobs[] = [
    &apos;name&apos; =&gt; &apos;workflow-check-conditions&apos;,
    &apos;command&apos; =&gt; &apos;$PHP_BIN vendor/bin/console workflow:check-condition&apos;,
    &apos;schedule&apos; =&gt; &apos;* * * * *&apos;,
    &apos;enable&apos; =&gt; true,
    &apos;stores&apos; =&gt; $allStores,
];

$jobs[] = [
    &apos;name&apos; =&gt; &apos;workflow-check-timeouts&apos;,
    &apos;command&apos; =&gt; &apos;$PHP_BIN vendor/bin/console workflow:check-timeout&apos;,
    &apos;schedule&apos; =&gt; &apos;* * * * *&apos;,
    &apos;enable&apos; =&gt; true,
    &apos;stores&apos; =&gt; $allStores,
];
```

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

Start an instance, then run `docker/sdk cli console workflow:check-condition` and confirm the instance advances in `spy_state_machine_process_definition_instance`.

{% endinfo_block %}

## Remove the demo workflow

The [B2B Demo Marketplace](/docs/about/all/spryker-marketplace/marketplace-b2b-suite.html) ships with the Workflows feature installed and with a demo `ExampleWorkflow` module. The module provides the `CompanyOnboarding` workflow and example triggers for the `Company`, `Merchant`, `MerchantRelationship`, and `MerchantProduct` subject types. If your project does not need the demo workflow or the example triggers, remove them as follows. The Workflows feature itself stays installed.

1. Delete the `src/Pyz/Zed/ExampleWorkflow/` directory.
2. In `src/Pyz/Zed/Workflow/WorkflowDependencyProvider.php`, remove the `ExampleWorkflow` plugins from `getCommandPlugins()`, `getConditionPlugins()`, and `getTriggerPlugins()`. Add your own plugins instead.
3. In `src/Pyz/Zed/Event/EventDependencyProvider.php`, remove the `StateMachineCompanyEventSubscriber`, `StateMachineMerchantEventSubscriberPlugin`, `StateMachineMerchantRelationshipEventSubscriberPlugin`, and `StateMachineMerchantProductEventSubscriberPlugin` subscribers.
4. Remove the demo workflow data: the `workflow.csv` files and the `workflow/company_onboarding.xml` definitions in `data/import/common/common/`, `data/import/b2b_common/common/`, and `data/import/b2b_robot/common/`. To provision your own workflows, replace them with your files as described in [Provide the import data](#provide-the-import-data).
5. If you do not provision workflows through data import, remove the `workflow` entry from the `data/import/local/*.yml` and `data/import/production/*.yml` import configurations.
6. In `phpmd.xml`, remove the exclusion for `ExampleWorkflow`.

The Propel `event` behavior on `spy_merchant_relationship` can stay: it only publishes entity events and does not depend on the demo module.

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

Run the install recipe and make sure the following applies:

- **Administration &gt; Workflows** does not list the `CompanyOnboarding` workflow.
- On the **Create workflow** page, **Subject type** lists only the subject types of your own trigger plugins.

{% endinfo_block %}
</description>
            <pubDate>Mon, 26 Oct 2026 15:30:43 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/integrate-and-configure/integrate-workflow-feature.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/integrate-and-configure/integrate-workflow-feature.html</guid>
            
            
        </item>
        
        <item>
            <title>Add a subject type to the Workflows feature</title>
            <description>&lt;p&gt;A workflow runs for one &lt;em&gt;subject type&lt;/em&gt;, such as &lt;code&gt;Company&lt;/code&gt; or &lt;code&gt;Merchant&lt;/code&gt;. Subject types come from your project code: the &lt;strong&gt;Subject type&lt;/strong&gt; field on the &lt;strong&gt;Create workflow&lt;/strong&gt; page lists a subject type only after your project registers at least one trigger plugin for it.&lt;/p&gt;
&lt;p&gt;This document describes how to add a subject type and start a workflow instance whenever an application event fires for it.&lt;/p&gt;
&lt;p&gt;The steps use the &lt;code&gt;MerchantRelationship&lt;/code&gt; subject type as an example. The same pattern applies to any entity in your project.&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Install the Workflows feature. For details, see &lt;a href=&quot;/docs/dg/dev/integrate-and-configure/integrate-workflow-feature.html&quot;&gt;Install the Workflows feature&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;how-a-subject-type-is-wired&quot;&gt;How a subject type is wired&lt;/h2&gt;
&lt;p&gt;Three pieces of project code connect an application event to a workflow:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PIECE&lt;/th&gt;
&lt;th&gt;PURPOSE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Trigger plugin&lt;/td&gt;
&lt;td&gt;Makes the subject type and its event selectable in the Back Office.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event listener&lt;/td&gt;
&lt;td&gt;Receives the application event and asks the Workflows feature to start an instance.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event subscriber&lt;/td&gt;
&lt;td&gt;Registers the listener for the event in the event dispatcher.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;When the event fires, &lt;code&gt;WorkflowFacade::startStateMachineInstance()&lt;/code&gt; finds every active workflow that has this event as a trigger and starts an instance on its active version. If an instance already exists for the same subject, no new instance is started.&lt;/p&gt;
&lt;h2 id=&quot;define-the-event-and-subject-type&quot;&gt;1) Define the event and subject type&lt;/h2&gt;
&lt;p&gt;Keep the event name and the subject type in one place, so the trigger plugin and the listener use the same values.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;src/Pyz/Zed/ExampleWorkflow/ExampleWorkflowConfig.php&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Pyz\Zed\ExampleWorkflow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\Kernel\AbstractBundleConfig&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;ExampleWorkflowConfig&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AbstractBundleConfig&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cd&quot;&gt;/**
     * Requires the Propel event behavior on spy_merchant_relationship.
     */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;EVENT_MERCHANT_RELATIONSHIP_CREATE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Entity.spy_merchant_relationship.create&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SUBJECT_TYPE_MERCHANT_RELATIONSHIP&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;MerchantRelationship&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;enable-entity-events-for-the-table&quot;&gt;2) Enable entity events for the table&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Entity.*&lt;/code&gt; events are published only for tables that have the Propel &lt;code&gt;event&lt;/code&gt; behavior. If your event is an entity event and the table does not publish it yet, add the behavior in the project schema. Skip this step for events that a module already publishes, such as &lt;code&gt;Merchant.created&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;src/Pyz/Zed/MerchantRelationship/Persistence/Propel/Schema/spy_merchant_relationship.schema.xml&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;table&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;spy_merchant_relationship&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;behavior&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;event&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;parameter&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;spy_merchant_relationship_all&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;column=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/behavior&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Apply the schema change:&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker/sdk cli console propel:install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;add-the-trigger-plugin&quot;&gt;3) Add the trigger plugin&lt;/h2&gt;
&lt;p&gt;The trigger plugin binds the event to the subject type. Its name and description are shown in the &lt;strong&gt;Triggers&lt;/strong&gt; field on the &lt;strong&gt;Create workflow&lt;/strong&gt; page.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;src/Pyz/Zed/ExampleWorkflow/Communication/Plugin/StateMachine/MerchantRelationshipCreateStateMachineProcessTriggerPlugin.php&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Pyz\Zed\ExampleWorkflow\Communication\Plugin\StateMachine&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\ExampleWorkflow\ExampleWorkflowConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\Kernel\Communication\AbstractPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\Workflow\Dependency\Plugin\StateMachineProcessTriggerPluginInterface&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;MerchantRelationshipCreateStateMachineProcessTriggerPlugin&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AbstractPlugin&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;StateMachineProcessTriggerPluginInterface&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getEventName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ExampleWorkflowConfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EVENT_MERCHANT_RELATIONSHIP_CREATE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Merchant relationship created&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getSubjectType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ExampleWorkflowConfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SUBJECT_TYPE_MERCHANT_RELATIONSHIP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getDescription&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Fired when a new merchant relationship is created.&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Register the plugin:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;src/Pyz/Zed/Workflow/WorkflowDependencyProvider.php&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Pyz\Zed\Workflow&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\ExampleWorkflow\Communication\Plugin\StateMachine\MerchantRelationshipCreateStateMachineProcessTriggerPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\Workflow\WorkflowDependencyProvider&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SprykerWorkflowDependencyProvider&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;WorkflowDependencyProvider&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SprykerWorkflowDependencyProvider&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cd&quot;&gt;/**
     * @return array&amp;lt;\Spryker\Zed\Workflow\Dependency\Plugin\StateMachineProcessTriggerPluginInterface&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;getTriggerPlugins&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;array&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MerchantRelationshipCreateStateMachineProcessTriggerPlugin&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;add-the-event-listener&quot;&gt;4) Add the event listener&lt;/h2&gt;
&lt;p&gt;The listener receives the event and starts the workflow instance. The subject identifier is the ID of the created entity.&lt;/p&gt;
&lt;p&gt;The listener needs &lt;code&gt;WorkflowFacadeInterface&lt;/code&gt;. Provide the facade in the module’s dependency provider:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;src/Pyz/Zed/ExampleWorkflow/ExampleWorkflowDependencyProvider.php&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Pyz\Zed\ExampleWorkflow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\Kernel\AbstractBundleDependencyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\Kernel\Container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ExampleWorkflowDependencyProvider&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AbstractBundleDependencyProvider&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FACADE_WORKFLOW&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;FACADE_WORKFLOW&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;provideCommunicationLayerDependencies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Container&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Container&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$container&lt;/span&gt; &lt;span class=&quot;o&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;provideCommunicationLayerDependencies&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$container&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;static&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;FACADE_WORKFLOW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Container&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$container&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getLocator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;workflow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;facade&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;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;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;Expose it through the module’s communication factory:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;src/Pyz/Zed/ExampleWorkflow/Communication/ExampleWorkflowCommunicationFactory.php&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Pyz\Zed\ExampleWorkflow\Communication&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\ExampleWorkflow\ExampleWorkflowDependencyProvider&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\Kernel\Communication\AbstractCommunicationFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\Workflow\Business\WorkflowFacadeInterface&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;ExampleWorkflowCommunicationFactory&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AbstractCommunicationFactory&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getWorkflowFacade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;WorkflowFacadeInterface&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getProvidedDependency&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ExampleWorkflowDependencyProvider&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;FACADE_WORKFLOW&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Add the listener:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;src/Pyz/Zed/ExampleWorkflow/Communication/Plugin/Event/Listener/StateMachineMerchantRelationshipEventListenerPlugin.php&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Pyz\Zed\ExampleWorkflow\Communication\Plugin\Event\Listener&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;Generated\Shared\Transfer\EventEntityTransfer&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;Generated\Shared\Transfer\StateMachineEventTriggerRequestTransfer&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\ExampleWorkflow\ExampleWorkflowConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Shared\Kernel\Transfer\TransferInterface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\Event\Dependency\Plugin\EventHandlerInterface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\Kernel\Communication\AbstractPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;cd&quot;&gt;/**
 * @method \Pyz\Zed\ExampleWorkflow\Communication\ExampleWorkflowCommunicationFactory getFactory()
 */&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;StateMachineMerchantRelationshipEventListenerPlugin&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AbstractPlugin&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EventHandlerInterface&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cd&quot;&gt;/**
     * @param string $eventName
     */&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;TransferInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$transfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$eventName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$transfer&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EventEntityTransfer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$transfer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&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;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getWorkflowFacade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;startStateMachineInstance&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;StateMachineEventTriggerRequestTransfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setEventName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ExampleWorkflowConfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EVENT_MERCHANT_RELATIONSHIP_CREATE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setSubjectType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ExampleWorkflowConfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SUBJECT_TYPE_MERCHANT_RELATIONSHIP&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;setIdentifier&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$transfer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getId&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;subscribe-the-listener-to-the-event&quot;&gt;5) Subscribe the listener to the event&lt;/h2&gt;
&lt;p&gt;Register the listener as queued, so starting the workflow does not slow down the request that created the entity. Queued listeners run when the &lt;code&gt;event&lt;/code&gt; queue is processed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;src/Pyz/Zed/ExampleWorkflow/Communication/Plugin/Event/Subscriber/StateMachineMerchantRelationshipEventSubscriberPlugin.php&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Pyz\Zed\ExampleWorkflow\Communication\Plugin\Event\Subscriber&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\ExampleWorkflow\Communication\Plugin\Event\Listener\StateMachineMerchantRelationshipEventListenerPlugin&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\ExampleWorkflow\ExampleWorkflowConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\Event\Dependency\EventCollectionInterface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\Event\Dependency\Plugin\EventSubscriberInterface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\Kernel\Communication\AbstractPlugin&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;StateMachineMerchantRelationshipEventSubscriberPlugin&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AbstractPlugin&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;EventSubscriberInterface&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getSubscribedEvents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;EventCollectionInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$eventCollection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$eventCollection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addListenerQueued&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;ExampleWorkflowConfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EVENT_MERCHANT_RELATIONSHIP_CREATE&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;StateMachineMerchantRelationshipEventListenerPlugin&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;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$eventCollection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Register the subscriber:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;src/Pyz/Zed/Event/EventDependencyProvider.php&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Pyz\Zed\Event&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\ExampleWorkflow\Communication\Plugin\Event\Subscriber\StateMachineMerchantRelationshipEventSubscriberPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\Event\EventDependencyProvider&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SprykerEventDependencyProvider&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;EventDependencyProvider&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SprykerEventDependencyProvider&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getEventSubscriberCollection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nv&quot;&gt;$eventSubscriberCollection&lt;/span&gt; &lt;span class=&quot;o&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;getEventSubscriberCollection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;nv&quot;&gt;$eventSubscriberCollection&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add&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;StateMachineMerchantRelationshipEventSubscriberPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$eventSubscriberCollection&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;section class=&apos;info-block info-block--warning&apos;&gt;&lt;i class=&apos;info-block__icon icon-warning&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;&lt;div class=&quot;info-block__title&quot;&gt;Verification&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;In the Back Office, go to &lt;strong&gt;Administration &amp;gt; Workflows&lt;/strong&gt; and click &lt;strong&gt;Create Workflow&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;In &lt;strong&gt;Subject type&lt;/strong&gt;, make sure &lt;code&gt;MerchantRelationship&lt;/code&gt; is available. Select it and make sure &lt;strong&gt;Merchant relationship created&lt;/strong&gt; is available in &lt;strong&gt;Triggers&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Create the workflow, then activate it and its version.&lt;/li&gt;
&lt;li&gt;Create a merchant relationship and process the event queue.&lt;/li&gt;
&lt;li&gt;Open the workflow’s active version and make sure a new instance for the merchant relationship appears in &lt;strong&gt;Workflow activity&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;&lt;/section&gt;
</description>
            <pubDate>Mon, 26 Oct 2026 15:30:43 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/integrate-and-configure/add-a-subject-type-to-the-workflows-feature.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/integrate-and-configure/add-a-subject-type-to-the-workflows-feature.html</guid>
            
            
        </item>
        
        <item>
            <title>AI Dev SDK</title>
            <description>&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;Warning&lt;/div&gt;
&lt;p&gt;Before you use AI-related tools, consult your legal department.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;
&lt;p&gt;When you use AI tools to write code, they rely on general patterns, which leads to mistakes and requires you to repeatedly explain your project structure.&lt;/p&gt;
&lt;p&gt;The AI Dev SDK makes your AI assistant Spryker-aware. It ships:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Skills and agents&lt;/strong&gt; — packaged workflows for Spryker work: project setup, demo preparation, feature customization, bugfixing, upgrades, testing, and code review. See &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;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rules and a project context file&lt;/strong&gt; — Spryker’s coding conventions, loaded when you work on the files they apply to, and your project’s specifics, loaded into every session.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An MCP server&lt;/strong&gt; (MCP — Model Context Protocol, a standard that lets AI tools query external systems) — live access to your running application: transfer objects, module interfaces, order management system (OMS) state machines, and read-only database queries. See &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;/ul&gt;
&lt;p&gt;The SDK is delivered by the &lt;code&gt;spryker-sdk/ai-dev&lt;/code&gt; module and, for Claude Code and GitHub Copilot CLI, by the &lt;code&gt;spryker-ai-dev-sdk&lt;/code&gt; plugin. To set either of them up, 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;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;/h2&gt;
&lt;p&gt;The skills, agents, and rules are files installed into your AI tool’s directories — through the plugin (for Claude Code and GitHub Copilot CLI) or the &lt;code&gt;ai-dev:setup&lt;/code&gt; console command (for other assistants). The assistant loads the context file into every session and each rule when you work on a file that the rule applies to. When you describe a task, it picks the matching skill and follows a tested Spryker workflow instead of improvising.&lt;/p&gt;
&lt;p&gt;The MCP server covers what static files cannot: facts about your running application. It runs as a console command inside your project’s Docker container; when the assistant needs a transfer structure, an OMS transition, or a data check, it queries the server and works with your actual project state instead of assumptions.&lt;/p&gt;
&lt;p&gt;The result: the generated code fits your project from the start, and you spend less time correcting mistakes and explaining Spryker concepts.&lt;/p&gt;
&lt;h2 id=&quot;key-capabilities&quot;&gt;Key capabilities&lt;/h2&gt;
&lt;h3 id=&quot;ready-made-workflows&quot;&gt;Ready-made workflows&lt;/h3&gt;
&lt;p&gt;Six orchestrated workflows drive multi-step work end to end, and the profiler workflow turns the WebProfiler into hard numbers. They are all documented under &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;:&lt;/p&gt;
&lt;ul&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; — turn a fresh demoshop clone into your project&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-demo-prep-wizard.html&quot;&gt;Demo Preparation Wizard&lt;/a&gt; — prepare a customer demo from the briefing to a handover report&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 a feature from a product requirement document to a committed branch&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; — drive a bug to a validated, QA-accepted fix&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; — upgrade a customized project to a newer Spryker release&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; — read the Spryker WebProfiler as hard performance numbers&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-architecture-workflow.html&quot;&gt;Architecture Workflow&lt;/a&gt; — turn project discovery into a filled &lt;code&gt;architecture/&lt;/code&gt; folder of arc42 sections and C4 diagrams&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;spryker-aware-code-review&quot;&gt;Spryker-aware code review&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;spryker-code-reviewer&lt;/code&gt; agent checks changes against Spryker’s coding standards and layer architecture before they reach a pull request.&lt;/p&gt;
&lt;h3 id=&quot;faster-spryker-specific-answers&quot;&gt;Faster Spryker-specific answers&lt;/h3&gt;
&lt;p&gt;AI can search Spryker documentation instead of requiring you to explain basic concepts or guessing how features work.&lt;/p&gt;
&lt;h3 id=&quot;smarter-code-generation&quot;&gt;Smarter code generation&lt;/h3&gt;
&lt;p&gt;AI can look up your actual transfer objects, module dependencies, and interface methods so that the generated code matches your project structure instead of relying on assumptions.&lt;/p&gt;
&lt;h3 id=&quot;oms-debugging-made-easy&quot;&gt;OMS debugging made easy&lt;/h3&gt;
&lt;p&gt;AI can analyze your OMS flows to find possible next states, transitions, conditions, and timeouts for any order or state. This capability is especially helpful when you work with complex OMS schemas. You no longer need to manually follow arrows in large diagrams.&lt;/p&gt;
&lt;h3 id=&quot;working-with-complex-data-imports&quot;&gt;Working with complex data imports&lt;/h3&gt;
&lt;p&gt;AI can analyze, modify, and transform multi-column CSV files correctly. This task normally requires significant manual effort.&lt;/p&gt;
&lt;h3 id=&quot;database-queries&quot;&gt;Database queries&lt;/h3&gt;
&lt;p&gt;AI can execute read-only database queries to inspect data when debugging issues.&lt;/p&gt;
&lt;h3 id=&quot;extensible-for-your-project&quot;&gt;Extensible for your project&lt;/h3&gt;
&lt;p&gt;You can extend the MCP server with custom plugins to add new tools — see &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-mcp-server.html#extension-points&quot;&gt;Extension points&lt;/a&gt;. The shipped skills are plain Markdown files, so you can also use them as a template for your team’s own workflows.&lt;/p&gt;
</description>
            <pubDate>Mon, 26 Oct 2026 09:25:38 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/ai/ai-dev/ai-dev.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/ai/ai-dev/ai-dev.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 seven ready-made workflows, more than 30 skills, and six agents that codify common Spryker practice — from turning a demoshop clone into a customer project and preparing a customer demo,
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)&lt;/li&gt;
&lt;li&gt;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 &lt;code&gt;spryker-ai-dev-sdk&lt;/code&gt; plugin&lt;/strong&gt; — for Claude Code and GitHub Copilot CLI users, the plugin installs the same skills and agents through the &lt;code&gt;spryker-plugins-official&lt;/code&gt; marketplace, no console command required. Both coding agents install the same plugin. See &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-installation.html&quot;&gt;Installation&lt;/a&gt;.&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 is 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;Seven 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-demo-prep-wizard.html&quot;&gt;Demo Preparation Wizard&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;demo-prep-wizard&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prepares a customer demo from the briefing to a handover report for the presenter&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;tr&gt;
&lt;td&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-architecture-workflow.html&quot;&gt;Architecture Workflow&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;architecture-prep&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Turns project discovery into a filled &lt;code&gt;architecture/&lt;/code&gt; folder&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;All but the Profiler Workflow 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-v2.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 six 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;Plugin (Claude Code and GitHub Copilot CLI)&lt;/strong&gt; — every skill is also available as a slash command, shown in the &lt;strong&gt;Command&lt;/strong&gt; column below. Type the command (for example &lt;code&gt;/spryker-ai-dev-sdk:spryker-customization&lt;/code&gt;) and the skill loads into the current chat.&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, GitHub Copilot in an IDE, 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. On a plugin install, the agent name carries the plugin prefix: &lt;code&gt;spryker-ai-dev-sdk:spryker-verifier&lt;/code&gt; in Claude Code, &lt;code&gt;spryker-ai-dev-sdk/spryker-verifier&lt;/code&gt; in GitHub Copilot.&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 plugin (Claude Code and GitHub Copilot CLI).&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;Command&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;&lt;code&gt;/spryker-ai-dev-sdk:project-starter-wizard&lt;/code&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-demo-prep-wizard.html&quot;&gt;&lt;code&gt;demo-prep-wizard&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:demo-prep-wizard&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prepare a customer demo on a demoshop clone: intake, requirements, design reference, harvest, build, check, and handover report&lt;/td&gt;
&lt;td&gt;The preparer confirms the scope once and answers all questions in one sitting; every demo point is checked in the browser; the handover report has one row per requirement of the brief, with its status&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/demo-prep-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;&lt;code&gt;/spryker-ai-dev-sdk:spryker-customization&lt;/code&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;&lt;code&gt;/spryker-ai-dev-sdk:spryker-bugfix&lt;/code&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-architecture-workflow.html&quot;&gt;&lt;code&gt;architecture-prep&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-architecture:architecture-prep&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Turn project discovery into a filled &lt;code&gt;architecture/&lt;/code&gt; folder of arc42 sections and C4 diagrams&lt;/td&gt;
&lt;td&gt;Progressive questionnaire that asks only what the project’s shape requires; mines documents you already have before asking; writes what the answers support and names the rest as owned gaps&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/plugins/spryker-architecture/skills/architecture-prep/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;&lt;code&gt;/spryker-ai-dev-sdk:spryker-upgrade&lt;/code&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; also updates the Docker SDK, the deploy files, and the developer tools, applies security and performance fixes, finds the release steps, and writes a report of the upgrade&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;Command&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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk: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; the &lt;code&gt;validate.php gate&lt;/code&gt; command runs all data checks in one call and 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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk: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;demo-preparation-skills&quot;&gt;Demo preparation skills&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;demo-prep-wizard&lt;/code&gt; runs these as its phases, and each also works standalone.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Command&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;demo-intake&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:demo-intake&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Turn a customer briefing — a &lt;code&gt;.docx&lt;/code&gt;, &lt;code&gt;.pdf&lt;/code&gt; or &lt;code&gt;.txt&lt;/code&gt; brief, a demo script, a reference URL, or a verbal brief — into structured input&lt;/td&gt;
&lt;td&gt;Read-only on the source; states what is known, names where the data is, and marks every unanswered question&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/demo-intake/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;harvest-source-materials&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:harvest-source-materials&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Collect product data, product images, editorial images, and brand assets from the customer’s public site or a supplied export&lt;/td&gt;
&lt;td&gt;Collects only what the demo needs, into a folder beside the project; records every limit of the collection&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/harvest-source-materials/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;match-reference-design&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:match-reference-design&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Make the storefront look like a reference site — homepage composition, navigation, and CMS blocks&lt;/td&gt;
&lt;td&gt;One change per cycle, each with a screenshot before and after; every step is reversible&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/match-reference-design/README.md&quot;&gt;README&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;demo-run-sheet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:demo-run-sheet&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Write the demo handover report: environment, demo accounts, products, categories, demo points, what to watch out for, and how each part is built&lt;/td&gt;
&lt;td&gt;Every link, login, price, and facet is read from the running shop and tested; HTML that keeps its format when pasted into a document, or Markdown&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/demo-run-sheet/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;Command&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;&lt;code&gt;/spryker-ai-dev-sdk: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; detects the coding agent (Claude Code, GitHub Copilot CLI, VS Code) and writes the files it reads; also refreshes the rules and context file from the latest upstream content, or installs only the rules without Docker and Composer&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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk:yves-atomic-frontend&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create and override atomic design components for the Yves storefront — Twig, SCSS, and TypeScript&lt;/td&gt;
&lt;td&gt;Components match the project’s atomic conventions; catches project-specific pitfalls such as an &lt;code&gt;index.ts&lt;/code&gt; that replaces the core entry or a same-name component that silently shadows a core one&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;backoffice-frontend&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:backoffice-frontend&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Build or override Back Office pages — Zed Twig templates, navigation and ACL, and Back Office JavaScript and SCSS&lt;/td&gt;
&lt;td&gt;Overrides extend core blocks instead of copying them; covers the cache warm-ups a new template, menu entry, or action needs before it shows up&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/backoffice-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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk: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;Command&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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk: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;&lt;code&gt;/spryker-ai-dev-sdk:spryker-profiler&lt;/code&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 plugin (Claude Code and GitHub Copilot CLI).&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;
</description>
            <pubDate>Mon, 26 Oct 2026 09:25:38 +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; plugin. Plugin version 0.7.0 adds the tooling alignment and the performance fixes. Plugin version 0.8.0 adds the security fixes, the release steps check, and the handover report, and supports GitHub Copilot.&lt;/p&gt;
&lt;p&gt;To update the 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. In GitHub Copilot, run &lt;code&gt;copilot plugin update spryker-ai-dev-sdk@spryker-plugins-official&lt;/code&gt;. For installation instructions, 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;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-v2.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;p&gt;For the full flow, the design decisions, and the known limits, see the &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;&lt;code&gt;spryker-upgrade&lt;/code&gt; README&lt;/a&gt; in the plugin repository.&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;Preflight and baselines&lt;/strong&gt; — boots the project before the upgrade and records snapshots of overrides, shadowed frontend files, constants, the Back Office pages, and the storage and search tables. It also records the existing test, PHPStan, and code style results, so that existing damage is not attributed to the upgrade. &lt;code&gt;check-baselines.php&lt;/code&gt; blocks the next phase until all baselines exist.&lt;/li&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;Target selection&lt;/strong&gt; — reads the installed state from &lt;code&gt;composer.lock&lt;/code&gt;, and reads the security release notes that Spryker published after the starting release.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tooling, Docker SDK, and deploy files&lt;/strong&gt; — updates the Docker SDK, the deploy files, and the developer tools (PHPStan, the evaluator, the code sniffer, Codeception) to the versions of the reference demo shop of the target release, each as its own commit.&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;Composer update&lt;/strong&gt; — runs a full update in the container, then applies the security fixes. See &lt;a href=&quot;#security-fixes&quot;&gt;Security fixes&lt;/a&gt;.&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;li&gt;&lt;strong&gt;Boot and verify&lt;/strong&gt; — boots the upgraded project, compares the storage and search tables with the baseline, regenerates the artifacts, and runs the full test suite in the booted environment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;New features&lt;/strong&gt; — lists the new packages and the new capabilities of existing modules. You decide for each one: integrate now, defer, or never.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance fixes&lt;/strong&gt; — checks the project against Spryker’s performance guidelines. See &lt;a href=&quot;#performance-fixes&quot;&gt;Performance fixes&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Page checks&lt;/strong&gt; — checks the pages of the upgraded project in the browser.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Release steps&lt;/strong&gt; — finds the commands that staging and production must run after the normal deployment. See &lt;a href=&quot;#release-steps&quot;&gt;Release steps&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reports&lt;/strong&gt; — writes a report for the developer and a handover report for the development team. See &lt;a href=&quot;#reports&quot;&gt;Reports&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The upgrade comes as two pull requests: the tests pull request first, then the upgrade pull request, which starts from it. Without new tests, there is only the upgrade pull request.&lt;/p&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;check-baselines.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Missing baselines before the upgrade starts, so every later comparison has something to compare with&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check-added-comments.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Explanatory comments and suppressions (&lt;code&gt;@phpstan-ignore&lt;/code&gt;, &lt;code&gt;eslint-disable&lt;/code&gt;, and others) added since the upgrade began&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check-platform-alignment.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A lock file resolved on a host that does not match the project’s image, and would not install there&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check-tooling-alignment.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Developer tools, the Docker SDK version, and the deploy files that do not match the reference demo shop of the target release&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check-constant-overrides.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Project constants that override a vendor constant whose value changed in the new version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check-legacy-css-classes.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Legacy CSS classes that the vendor code still renders or selects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;storage-search-counts.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Storage and search tables that lost rows after the same import, compared before and after the upgrade&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backoffice-smoke.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Back Office navigation pages and table data endpoints that no longer answer — a vendor-to-vendor conflict can break many pages that the upgrade did not touch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;check-performance.php&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Performance guideline pages without a recorded fix or a reason, and fixes without a size, a status, or the developer’s decision&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;security-fixes&quot;&gt;Security fixes&lt;/h2&gt;
&lt;p&gt;The skill reads the security release notes that Spryker published after the starting release — for example, &lt;a href=&quot;/docs/about/all/releases/security-releases/security-release-notes-202610.0.html&quot;&gt;Security release notes 202610.0&lt;/a&gt;. For each issue, it checks the installed module version in &lt;code&gt;composer.lock&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The module is below its fixed version&lt;/strong&gt; — the skill raises the module’s constraint to the fixed version and updates it, as its own commit. When that does not resolve, the skill gives the developer the output of &lt;code&gt;composer why-not&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The fix needs extra steps&lt;/strong&gt; — a module update alone does not always fix an issue. The skill applies the code and configuration steps as their own commit. Steps for the environments go into the release steps.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The project overrides a file that the fix changed&lt;/strong&gt; — the skill does not change the override, because that can change the shop’s behavior. The report lists the issue as not fixed, with the recommended fix for that file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When the release notes cannot be read, the reports say that the security fixes are not checked.&lt;/p&gt;
&lt;h2 id=&quot;performance-fixes&quot;&gt;Performance fixes&lt;/h2&gt;
&lt;p&gt;The skill reads Spryker’s &lt;a href=&quot;/docs/dg/dev/guidelines/performance-guidelines/performance-guidelines.html&quot;&gt;performance guidelines&lt;/a&gt; as published on the day of the upgrade. For each guideline page, it records the fixes that apply to the project, or the reason why none applies. Each fix gets a size:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;S&lt;/strong&gt; — a configuration value or a flag. The skill applies it without asking.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;M&lt;/strong&gt; — a package update or a change in a few files.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L&lt;/strong&gt; — a refactoring, a schema change, or a change in many files.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the M and L fixes, you decide once for each fix: apply now, defer, or skip. A fix that changes the shop’s visible behavior is not applied; the report lists it as a recommendation. The skill applies one fix at a time, as its own commit, and reverts it when the checks fail. The deferred fixes go into the report’s performance backlog.&lt;/p&gt;
&lt;h2 id=&quot;release-steps&quot;&gt;Release steps&lt;/h2&gt;
&lt;p&gt;Through the whole upgrade, the skill writes each release action into a log: a migration, an environment variable, a one-time command, a new job, or an action that a guide names — a migration guide, the integration guide of a new feature, a performance fix, or a security release note.&lt;/p&gt;
&lt;p&gt;Then it checks the log locally. It deploys the upgrade branch onto a copy of the production state with only the normal deployment commands, and compares the result with a fresh installation of the upgrade branch. The database, Redis, and the search engine are compared. Each difference that a command fixes becomes a release step. Before the check, the skill asks which branch production runs, which commands the deployment runs, and whether it may reset the local data.&lt;/p&gt;
&lt;h2 id=&quot;reports&quot;&gt;Reports&lt;/h2&gt;
&lt;p&gt;The skill writes two reports in &lt;code&gt;.spryker-upgrade/state/&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The developer report&lt;/strong&gt; — for the person who ran the upgrade: what is verified, what has not failed yet, the tests that were already red before the upgrade, the replaced deprecated plugins, and the performance fixes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The handover report&lt;/strong&gt; — for the development team that reviews, releases, and owns the result. The skill asks once for the format: Markdown, or HTML to paste into Confluence. It contains:
&lt;ul&gt;
&lt;li&gt;An executive summary: the goal, what was done, the changes in behavior, the security fixes, and what happens next.&lt;/li&gt;
&lt;li&gt;Technical details: the pull requests, the upgrade per area, the behavior changes from replaced deprecated plugins, the database migrations, and the security fixes.&lt;/li&gt;
&lt;li&gt;Action items for the development team: the review, the local environment, the release steps for staging and production, and what was not done in this upgrade.&lt;/li&gt;
&lt;li&gt;The existing issues found during the upgrade, the performance guideline check, and the new Spryker features that the project can adopt.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;A running Docker environment of the project (&lt;code&gt;docker/sdk&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;An AI tool with the SDK’s skills loaded — either through the plugin in Claude Code or GitHub Copilot CLI, or via &lt;code&gt;ai-dev:setup&lt;/code&gt; for another supported tool. See &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-installation.html&quot;&gt;Installation&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
</description>
            <pubDate>Mon, 26 Oct 2026 09:25:38 +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>Rules</title>
            <description>&lt;p&gt;The AI Dev SDK installs a set of &lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/tree/master/plugins/spryker-ai-dev-sdk/rules&quot;&gt;Spryker-specific coding rules&lt;/a&gt; into your project. These rules guide the AI to follow Spryker conventions automatically, without requiring you to explain them in every prompt.&lt;/p&gt;
&lt;h2 id=&quot;how-the-rules-reach-your-project&quot;&gt;How the rules reach your project&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;When a session starts&lt;/strong&gt; — in a Spryker project without the rules, the plugin offers once to install them. If you accept, the coding agent installs them. The offer does not show in CI, outside a Spryker project, or when the project has a &lt;code&gt;.ai-dev/no-rules-sync&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;With the &lt;code&gt;ai-dev-setup&lt;/code&gt; skill&lt;/strong&gt; — the full setup installs the rules together with the project context file and the MCP server. Ask for &lt;em&gt;“only the rules”&lt;/em&gt; to install only the rules; this flow needs no Docker environment and no Composer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;With the &lt;code&gt;ai-dev:setup&lt;/code&gt; console command&lt;/strong&gt; — for assistants without the plugin. See &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-installation.html#set-up-with-another-assistant&quot;&gt;Set up with another assistant&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The rules go to the folder that your coding agent reads:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Coding agent&lt;/th&gt;
&lt;th&gt;Rules folder&lt;/th&gt;
&lt;th&gt;Scope by file path&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.claude/rules/&amp;lt;rule&amp;gt;.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;paths&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Copilot&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.github/instructions/&amp;lt;rule&amp;gt;.instructions.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;applyTo&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A refresh never overwrites a rule file that you changed. Commit the rule files, so that the whole team works with the same rules.&lt;/p&gt;
&lt;h2 id=&quot;rule-files&quot;&gt;Rule files&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule file&lt;/th&gt;
&lt;th&gt;What it enforces&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;business-models.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Business model structure and responsibilities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;client-zed-communication.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Client–Zed gateway communication patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controller.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controller conventions and responsibilities, including translatable flash messages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dependency-provider.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dependency provider wiring and plugin stacks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enforce-constants-for-control-flow.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use of constants instead of magic strings in control flow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;expander-pattern.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Expander pattern for extending transfer objects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;factory-pattern.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Factory class structure and dependency injection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;form-data-loading-performance.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Performant data loading in Zed forms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;layer-communication.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cross-layer call rules (Presentation → Communication → Business → Persistence)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mapper-pattern.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mapper pattern for transfer-to-transfer and entity-to-transfer mappings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;merchant-portal-angular.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Merchant Portal Angular components: reuse of installed &lt;code&gt;@spryker/*&lt;/code&gt; UI components, project-side extension of core, and validation with the &lt;code&gt;mp:*&lt;/code&gt; scripts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;module-config.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Module configuration class conventions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;naming-conventions.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Class, method, and variable naming standards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;owasp.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OWASP security guidelines applied to Spryker code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;performance.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Performance best practices (query optimization, caching)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;persistence.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Persistence layer conventions (repositories, entity managers)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;php-code-style.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PHP code style rules (PSR compliance, formatting)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;plugins.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Plugin and plugin interface implementation patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;table.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Back Office table and query container conventions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transfer-object.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transfer object usage and immutability rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;upgradability.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Backward compatibility and upgradability guidelines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;yves-frontend.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yves storefront Twig, SCSS, and TypeScript: project-level overrides of core components, BEM and JavaScript hook conventions, and validation with the project’s npm lint scripts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zed-backoffice-frontend.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Back Office Twig, JavaScript, SCSS, and navigation: overrides that extend core blocks, Bootstrap 5 markup, no new jQuery, translated text, safe escaping, and server-side ACL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Every rule is scoped by file path: the coding agent loads a rule only when you work on a file that the rule applies to.&lt;/p&gt;
</description>
            <pubDate>Mon, 26 Oct 2026 09:25:38 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/ai/ai-dev/ai-dev-rules.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/ai/ai-dev/ai-dev-rules.html</guid>
            
            
        </item>
        
    </channel>
</rss>
