<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Spryker Documentation</title>
        <description>Spryker documentation center.</description>
        <link>https://docs.spryker.com/</link>
        <atom:link href="https://docs.spryker.com/feed.xml" rel="self" type="application/rss+xml"/>
        <lastBuildDate>Tue, 31 Mar 2026 08:52:36 +0000</lastBuildDate>
        <generator>Jekyll v4.2.2</generator>
        
        
        <item>
            <title>Serialization</title>
            <description>API Platform uses the [Spryker Serializer module](/docs/dg/dev/guidelines/serializer-guidelines.html) for converting between PHP objects back and forth. The Serializer is registered as a Symfony service and integrates directly with API Platform&apos;s serialization pipeline.

## How it works

When a request hits an API Platform endpoint:

1. **Request deserialization** — the incoming payload is deserialized into the resource object using the Serializer. (API Platform)
2. **Provider/Processor** — your provider or processor works with the resource object, converts to array and or Transfer objects, and vice versa.
3. **Response serialization** — the resource object is serialized back into the expected response format. (API Platform)

API Platform handles this automatically. You interact with the Serializer only when you need to customize serialization behavior.

## Customizing serialization context

Use `SerializerContextTransfer` to control how data is serialized in your providers or processors. Common use cases:

- **Serialization groups** — control which properties are included in the response
- **Skip null values** — omit null properties from the output
- **DateTime formatting** — set a custom date/time format

For the full list of available context options, see the [Serializer guidelines](/docs/dg/dev/guidelines/serializer-guidelines.html).

## Custom normalizers

To add a custom normalizer that applies to API Platform resources, implement `SerializerNormalizerPluginInterface` and register it in `SerializerDependencyProvider`. Custom normalizers take priority over built-in normalizers.

For details on the Symfony Serializer component, see the [Symfony documentation](https://symfony.com/doc/current/serializer.html).
</description>
            <pubDate>Mon, 30 Mar 2026 13:08:44 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/architecture/api-platform/serialization.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/architecture/api-platform/serialization.html</guid>
            
            
        </item>
        
        <item>
            <title>Data import optimization guidelines</title>
            <description>&lt;p&gt;This article will define the best practices for Spryker modules that need to work with two infrastructure concepts like DataImport and Publish &amp;amp; Synchronize. After several reviews and tests, we found that we need to define these rules to help developers to write more scalable and high-performance code when they implementing their features. Most of the time when developers create features, they don’t consider very high traffic or heavy data processing for big data. This article is going to describe all necessary requirements when you want to create new features and they should work with big data, for example, you want to create a new DataImport or a new P&amp;amp;S module which save millions of entities into a database in a very short amount of time.&lt;/p&gt;
&lt;h2 id=&quot;rules-for-data-importers&quot;&gt;Rules for data importers&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;DataImport&lt;/code&gt; module is responsible for reading data from different formats like CSV, JSON, etc and import them into a database with proper Spryker data structure. Usually importing of data is a time-consuming process and we need to follow some best practices to increase the performance. Here you will find some of them:&lt;/p&gt;
&lt;h3 id=&quot;progress-bar&quot;&gt;Progress bar&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;--progress-bar&lt;/code&gt; flag lets you monitor data import in real time using the &lt;a href=&quot;https://symfony.com/doc/current/components/console/helpers/progressbar.html&quot;&gt;Symfony Console Progress Bar&lt;/a&gt; component. While the import runs, the progress bar displays the current memory usage and processing speed, so you can identify bottlenecks without adding custom logging.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Update the &lt;code&gt;spryker/data-import&lt;/code&gt; package to version 1.32.0 or later:&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;composer update spryker/data-import:&lt;span class=&quot;s2&quot;&gt;&quot;^1.32.0&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Usage&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Add the &lt;code&gt;--progress-bar&lt;/code&gt; flag to any data import command:&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;console data:import combined-product-abstract &lt;span class=&quot;nt&quot;&gt;--progress-bar&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;Performance note&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;--progress-bar&lt;/code&gt; flag enables debug mode, which adds overhead to the import process. Use it for diagnostics and testing, not for production data imports.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;h3 id=&quot;single-vs-batch-operation&quot;&gt;Single vs batch operation&lt;/h3&gt;
&lt;p&gt;Assuming you are writing a data importer for &lt;code&gt;ProductAbstract&lt;/code&gt;, you might want to find a category for each product abstract, your very basic code would be something like this:&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;importProductAbstract&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;DataSetInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$dataSet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$categoryKey&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$dataSet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;category_key&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$productCategory&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SpyCategoryQuery&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;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;findOneByCategoryKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$categoryKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$dataSet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;product_category&apos;&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;$productCategory&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;importProductCategories&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$dataSet&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;This will work fine and you already achieved to your goals, but can you see the problem here?&lt;/p&gt;
&lt;p&gt;Here is the problem:&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;importProductAbstract&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;DataSetInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$dataSet&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;mf&quot;&gt;...&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$productCategory&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SpyCategoryQuery&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;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;findOneByCategoryKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$categoryKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// expensive call&lt;/span&gt;
 &lt;span class=&quot;mf&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;importProductAbstract&lt;/code&gt; method will call for each line of your CSV, imagine if you have one CSV file with 1,000,000 lines, it means you will run this query again and again for 1 million times! The possible solution is to avoid single processing and implementing a batch query for it.&lt;/p&gt;
&lt;p&gt;The current solution for DataImport is to add another step before the main step to gather all the information you need for the next steps. like querying all categories and remember them in memory and the next step can do a fast PHP look up from that result in memory.&lt;/p&gt;
&lt;section class=&apos;info-block info-block--warning&apos;&gt;&lt;i class=&apos;info-block__icon icon-warning&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;&lt;div class=&quot;info-block__title&quot;&gt;Note&lt;/div&gt;
&lt;p&gt;&lt;code&gt;DataImport&lt;/code&gt; v1.4.x doesn’t support the batch processing by default and the next version will provide a very clear solution for it.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;h3 id=&quot;orm-vs-pdo&quot;&gt;ORM vs PDO&lt;/h3&gt;
&lt;p&gt;ORM (Object-relational mapping) is a very good approach when we are working on very big and complex software but it’s not always the answer to all problems especially when it comes to batch processing. ORM is slow by design as they need to handle so many internal hydration and mapping.&lt;/p&gt;
&lt;p&gt;Here is an example of using the Propel ORM, this is a very clean and nice approach but not optimized enough for importing big data.&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;createOrUpdateProductAbstract&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;DataSetInterface&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$dataSet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;SpyProductAbstract&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;$productAbstractEntityTransfer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getProductAbstractTransfer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$dataSet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nv&quot;&gt;$productAbstractEntity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SpyProductAbstractQuery&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;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;filterBySku&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$productAbstractEntityTransfer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getSku&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;findOneOrCreate&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;save&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;$productAbstractEntity&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;This approach has two problems:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It runs two queries, one SELECT for &lt;code&gt;findOneOrCreate&lt;/code&gt; and one INSERT or UPDATE for &lt;code&gt;save&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Single process approach (repeated per each entry)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The solution is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Avoid ORM, you can use a very simple raw SQL but you should also avoid complex raw or big raw SQL queries.&lt;/li&gt;
&lt;li&gt;Implement &lt;code&gt;DataSetWriterInterface&lt;/code&gt; to achieve the batch processing approach, prepare one by one and write them once (Take a look at &lt;code&gt;ProductAbstractWriter&lt;/code&gt; in &lt;code&gt;Dataimport&lt;/code&gt; as an example).&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;facade-calls&quot;&gt;Facade calls&lt;/h3&gt;
&lt;p&gt;Sometimes you may need to run some validation or business logic for each data set, the easiest and safest way would be a Facade API call, that’s totally fine, but then imagine if these APIs also call some heavy queries very deep! this has a huge side effect on your performance during importing millions of data.&lt;/p&gt;
&lt;p&gt;Here you can see for each product stock, there are two facade calls, each facade call may run more than 5 queries, this means for importing 1,000,000 data, you will have 10,000,000 queries! (this will never finish!).&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;protected&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;updateBundleAvailability&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;foreach&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;nv&quot;&gt;$stockProductCollection&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$stockProduct&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;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;$stockProduct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ProductStockHydratorStep&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;KEY_IS_BUNDLE&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;continue&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;n&quot;&gt;productBundleFacade&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;updateBundleAvailability&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$stockProduct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ProductStockHydratorStep&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;KEY_CONCRETE_SKU&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;n&quot;&gt;productBundleFacade&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;updateAffectedBundlesAvailability&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$stockProduct&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ProductStockHydratorStep&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;KEY_CONCRETE_SKU&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;The solution is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Implement a new Facade API for batch operation&lt;/li&gt;
&lt;li&gt;Only call facade if they are very lightweight and they don’t run any query to a database&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;memory-management&quot;&gt;Memory management&lt;/h3&gt;
&lt;p&gt;Let’s say you already started to work with the batch operation, one of the challenges would be to keep the memory as low as possible. Sometimes you create variables and try to remember them always, but you may need them only until the end of the batch operation, so it’s better to release them as soon as possible.&lt;/p&gt;
&lt;h3 id=&quot;database-vendor-approach&quot;&gt;Database vendor approach&lt;/h3&gt;
&lt;p&gt;Spryker supports PostgreSQL, MySQL, and MariaDB. When working with databases, it’s good to know their related features. For example, one of the great features we like is &lt;a href=&quot;https://www.postgresql.org/docs/9.1/queries-with.html&quot;&gt;CTE&lt;/a&gt;. If you are inserting or updating big amounts of data, like millions of millions, use CTE as a replaceable for multiple inserts and updates. You can find examples of implementations in our &lt;a href=&quot;/docs/about/all/about-spryker.html#demo-shops&quot;&gt;Demo Shops&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;rules-for-publish-and-synchronize&quot;&gt;Rules for Publish and Synchronize&lt;/h2&gt;
&lt;p&gt;P&amp;amp;S is a concept for transferring data from Zed database to Yves databases like key-value storage (Redis or Valkey) and ES, This operation is separated into two isolated processes which call &lt;strong&gt;Publish&lt;/strong&gt; and &lt;strong&gt;Synchronize&lt;/strong&gt;. Publishing is a process to aggregating data and writing it to Database and Queue. Synchronization is a process to read an aggregated message from a queue and write it to external endpoints. The performance issues mostly come from &lt;strong&gt;Publishing&lt;/strong&gt; part. Again we need to follow the best practices to increase the performance. Here you will find some of them:&lt;/p&gt;
&lt;h3 id=&quot;single-vs-batch-operation-1&quot;&gt;Single vs batch operation&lt;/h3&gt;
&lt;p&gt;When you are creating a new listener you should consider these rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run your logic against a chunk of event messages not per each.&lt;/li&gt;
&lt;li&gt;Don’t run the query inside of for-loop&lt;/li&gt;
&lt;li&gt;Try to save them with a bulk operation, not one by one&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Take a look at this example:&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;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;publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$productAbstractIds&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;foreach&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$productAbstractIds&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$idProductAbstract&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;$productAbstractProductListStorageEntity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;queryProductAbstractProductListEntity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$idProductAbstract&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;Here we are passing a set of ids and then we try to run a query for each product abstract. Imagine if you have 2000 events as a chunk, then you have 2000 queries to a database.&lt;/p&gt;
&lt;p&gt;We can easily fix this by changing the query and run query only once per 2000 ids.&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$productAbstractIds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
     &lt;span class=&quot;nv&quot;&gt;$productAbstractProductListStorageEntities&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;queryProductAbstractProductListEntities&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$productAbstractIds&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;facade-calls-1&quot;&gt;Facade calls&lt;/h3&gt;
&lt;p&gt;Another point that we need to be very careful here is to call Facade API without any thinking through, we must make sure that these APIs will not run queries inside same as DataImport rule. You are allowed to call Facade API but if:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There is no query inside&lt;/li&gt;
&lt;li&gt;Facade API designed for batch operation (&lt;code&gt;findPriceForSku&lt;/code&gt; vs &lt;code&gt;findPricesForSkus&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;triggering-events&quot;&gt;Triggering events&lt;/h3&gt;
&lt;p&gt;DataImporters are triggering events manually, this is happening because of performance reasons :&lt;/p&gt;
&lt;p&gt;Triggering events automatically will generate so many duplicates events during data importing, (e.g: Inserting a Product into &lt;code&gt;spy_product&lt;/code&gt; and &lt;code&gt;spy_product_localized_attribute&lt;/code&gt; table will generate two events with the same &lt;code&gt;id_product&lt;/code&gt;).
Events will be triggered one by one.&lt;/p&gt;
&lt;p&gt;You can always switch the Event Triggering process with two methods:&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Zed\EventBehavior\EventBehaviorConfig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;mf&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;foo&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;cd&quot;&gt;/**
         * Disable the events triggering automatically
         */&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;EventBehaviorConfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;disableEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// 1.Create many entities in multiple tables (e.g API bulk import, nightly update jobs)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// 2.Trigger proper events if it&apos;s necessary ($eventFacade-&amp;gt;triggerBulk(&apos;ProductAbstractPublish&apos;, $eventTransfers))&lt;/span&gt;

        &lt;span class=&quot;cd&quot;&gt;/**
         * Enable the events triggering automatically
         */&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;EventBehaviorConfig&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;enableEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;pamps-and-cte&quot;&gt;P&amp;amp;S and CTE&lt;/h3&gt;
&lt;p&gt;Sometimes the amount of the data which needs be synced is very high, for this reason, we can not rely on a standard ORM solution for storing data in the database tables. we recommend you to use bulk insert operation whenever you have more than hundreds of thousand of data. you can still use the CTE technique which was used in DataImporter before. Spryker suite comes with several examples of using CTE technique in Storage and Search module you can replace them by overwriting the Business Factory in the modules:&lt;/p&gt;
&lt;section class=&apos;info-block info-block--warning&apos;&gt;&lt;i class=&apos;info-block__icon icon-warning&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;&lt;div class=&quot;info-block__title&quot;&gt;Note&lt;/div&gt;
&lt;p&gt;These examples only tested for PostgreSQL.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src/Pyz/Zed/PriceProductStorage/Business/Storage/PriceProductAbstractStorageWriter.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/Pyz/Zed/PriceProductStorage/Business/Storage/PriceProductConcreteStorageWriter.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/Pyz/Zed/ProductPageSearch/Business/Publisher/ProductAbstractPagePublisher.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/Pyz/Zed/ProductPageSearch/Business/Publisher/ProductConcretePageSearchPublisher.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/Pyz/Zed/ProductStorage/Business/Storage/ProductAbstractStorageWriter.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/Pyz/Zed/ProductStorage/Business/Storage/ProductConcreteStorageWriter.php&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src/Pyz/Zed/UrlStorage/Business/Storage/UrlStorageWriter.php&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example classes are going to be replaced with a Core CTE solution.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;When we are facing some batch operation, we need to think about big data and performance under heavy loading, we are not allowed to write same code that only does the job, it needs to be scalable and fast for high usages. Below you can find our main points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create batch queries and processes&lt;/li&gt;
&lt;li&gt;Don’t use ORM for batch processing as it’s slow by design&lt;/li&gt;
&lt;li&gt;Don’t run separated queries for each data-set&lt;/li&gt;
&lt;li&gt;Don’t call any facade logic if they are slow or run internal queries&lt;/li&gt;
&lt;li&gt;Release memory after each bulk operations to prevent memory issues&lt;/li&gt;
&lt;li&gt;Use CTE technique (supported by PostgreSQL or MySQL &amp;gt;= 8, and MariaDB &amp;gt;= 10.2)&lt;/li&gt;
&lt;/ul&gt;
</description>
            <pubDate>Mon, 30 Mar 2026 08:28:02 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/data-import/latest/data-import-optimization-guidelines.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/data-import/latest/data-import-optimization-guidelines.html</guid>
            
            
        </item>
        
        <item>
            <title>Serializer guidelines</title>
            <description>The Serializer module provides a Spryker-native wrapper around the [Symfony Serializer component](https://symfony.com/doc/current/serializer.html). It exposes serialization operations through the Spryker Service layer and supports extension via plugins.

## Operations

The `SerializerServiceInterface` provides four operations:

| Method | Description |
|---|---|
| `serialize()` | Converts data to a string format (JSON, XML, CSV) |
| `deserialize()` | Converts a string into a typed object |
| `normalize()` | Converts an object or data structure to an associative array |
| `denormalize()` | Converts an array into a typed object |

All operations accept an optional `SerializerContextTransfer` to configure behavior.

## SerializerContextTransfer

The `SerializerContextTransfer` maps Spryker transfer conventions to Symfony Serializer context options. Supported options include:

- **groups** — serialization groups for attribute filtering
- **isSkipNullValues** — omit null properties from output
- **isSkipUninitializedValues** — omit uninitialized properties
- **isPreserveEmptyObjects** — keep empty objects as `{}` instead of `[]`
- **isEnableMaxDepth** — enable max depth handling
- **datetimeFormat** — custom date/time format string
- **datetimeTimezone** — timezone for date/time normalization
- **defaultConstructorArguments** — default constructor arguments for denormalization
- **symfonyContext** — raw Symfony context array for advanced use cases (overrides explicit properties)

For the full list of Symfony context options, see the [Symfony Serializer documentation](https://symfony.com/doc/current/serializer.html#context).

## Extension via plugins

The module supports two plugin interfaces for registering custom normalizers and encoders:

- `SerializerNormalizerPluginInterface` — provides additional normalizers/denormalizers
- `SerializerEncoderPluginInterface` — provides additional encoders/decoders

Register plugins in your project&apos;s `SerializerDependencyProvider`:

```php
protected function getSerializerNormalizerPlugins(): array
{
    return [
        new YourCustomNormalizerPlugin(),
    ];
}
```

Custom normalizers are prepended before built-in normalizers, giving them higher priority.

## Built-in support

The module includes the following Symfony normalizers and encoders out of the box:

**Normalizers:** ObjectNormalizer, ArrayDenormalizer, DateTimeNormalizer, DateTimeZoneNormalizer, DateIntervalNormalizer, BackedEnumNormalizer, DataUriNormalizer, JsonSerializableNormalizer, UidNormalizer, UnwrappingDenormalizer

**Encoders:** JsonEncoder, XmlEncoder, CsvEncoder
</description>
            <pubDate>Mon, 30 Mar 2026 06:26:35 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/guidelines/serializer-guidelines.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/guidelines/serializer-guidelines.html</guid>
            
            
        </item>
        
        <item>
            <title>Guidelines</title>
            <description>This section contains a collection of useful guidelines for developing on the Spryker Commerce OS:  

- [Coding guidelines](/docs/dg/dev/guidelines/coding-guidelines/coding-guidelines.html)
- [Keeping a project upgradable](/docs/dg/dev/guidelines/keeping-a-project-upgradable/keeping-a-project-upgradable.html)
- [Performance guidelines](/docs/dg/dev/guidelines/performance-guidelines/general-performance-guidelines.html)
- [Testing guidelines](/docs/dg/dev/guidelines/testing-guidelines/testing-best-practices/testing-concepts.html)
- [Project development guidelines](/docs/dg/dev/guidelines/project-development-guidelines.html)
- [Data processing guidelines](/docs/dg/dev/guidelines/data-processing-guidelines.html)
- [Module configuration convention](/docs/dg/dev/guidelines/module-configuration-convention.html)
- [Security guidelines](/docs/dg/dev/guidelines/security-guidelines.html)
- [Serializer guidelines](/docs/dg/dev/guidelines/serializer-guidelines.html)
</description>
            <pubDate>Mon, 30 Mar 2026 06:26:35 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/guidelines/guidelines.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/guidelines/guidelines.html</guid>
            
            
        </item>
        
        <item>
            <title>Ratings and Reviews</title>
            <description>Drive sales by including user reviews and ratings. Reviews and ratings are a proven sign of trust; they allow brands to receive valuable and moderate feedback in the Administration Interface. The Ratings and Reviews feature also comes with the functionality to add text-free reviews and star ratings.

## Video tutorial

For more details about managing ratings and reviews, check the video:


&lt;figure class=&quot;video_container&quot;&gt;
    &lt;video width=&quot;100%&quot; height=&quot;auto&quot; controls&gt;
      &lt;source src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/pbc/all/ratings-reviews/ratings-and-reviews.md/How+to+Manage+Ratings+and+Reviews+in+Spryker+B2C-efvyq9vfb8.mp4&quot; type=&quot;video/mp4&quot;&gt;
  &lt;/video&gt;
&lt;/figure&gt;


## Ratings &amp; Reviews capabilities available in Spryker

| NAME | MARKETPLACE COMPATIBLE |
| --- | --- |
| Spryker | No |

## Current constraints

The feature has the following functional constraints, which are going to be resolved in the future:
- Product reviews are linked to locales but not stores.
- A review is available in all the stores that share the locale of the store in which it has been originally created.


## Related Business User documents

| BACK OFFICE USER GUIDES |
| - |
| [Manage product reviews in the Back Office](/docs/pbc/all/ratings-reviews/latest/manage-product-reviews-in-the-back-office.html) |


## Related Developer documents

| INSTALLATION GUIDES | GLUE API GUIDES  | DATA IMPORT | TUTORIALS AND HOWTOS |
|---------|---------|---------| - |
| [Install the Product Rating and Reviews feature](/docs/pbc/all/ratings-reviews/latest/install-and-upgrade/install-the-product-rating-and-reviews-feature.html) | [Managing product ratings and reviews using Glue API](/docs/pbc/all/ratings-reviews/latest/manage-using-glue-api/glue-api-manage-product-reviews.html)  | [File details: product_review.csv](/docs/pbc/all/ratings-reviews/latest/import-and-export-data/import-file-details-product-review.csv.html)  | [HowTo: Configure product reviews](/docs/pbc/all/ratings-reviews/latest/tutorials-and-howtos/howto-configure-product-reviews.html) |
| [Install the Product Rating and Reviews Glue API](/docs/pbc/all/ratings-reviews/latest/install-and-upgrade/install-the-product-rating-and-reviews-glue-api.html)   | [Retrieve product reviews when retrieving abstract products](/docs/pbc/all/ratings-reviews/latest/manage-using-glue-api/glue-api-retrieve-product-reviews-when-retrieving-abstract-products.html)  |  | |
| [Install the Product Rating and Reviews + Product Group feature](/docs/pbc/all/ratings-reviews/latest/install-and-upgrade/install-the-product-rating-and-reviews-product-group-feature.html) | [Retrieving product reviews when retrieving concrete products](/docs/pbc/all/ratings-reviews/latest/manage-using-glue-api/glue-api-retrieve-product-reviews-when-retrieving-concrete-products.html) | | |
</description>
            <pubDate>Thu, 26 Mar 2026 10:56:54 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/ratings-reviews/latest/ratings-and-reviews.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/ratings-reviews/latest/ratings-and-reviews.html</guid>
            
            
        </item>
        
        <item>
            <title>Migrate from the ACP Vertex app</title>
            <description>This document describes how to migrate from the MessageBroker-based [ACP Vertex](https://docs-archive.spryker.com/docs/pbc/all/tax-management/202507.0/base-shop/third-party-integrations/vertex/vertex) integration to the direct `spryker-eco/vertex` module.

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

The tax calculation logic remains the same. The ECO module communicates directly with the Vertex API from your application instead of going through the MessageBroker.

{% endinfo_block %}

## 1. Install and integrate the module

Follow the [Integrate Vertex](/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/install-vertex/integrate-vertex.html) guide to install and set up the module.

## 2. Remove old ACP plugins and configuration

### 2a. Remove the TaxApp MessageBroker handler

In `src/Pyz/Zed/MessageBroker/MessageBrokerDependencyProvider.php`, remove the following import and plugin registration:

```php
// Remove this use statement:
use Spryker\Zed\TaxApp\Communication\Plugin\MessageBroker\TaxAppMessageHandlerPlugin;

// Remove from getMessageHandlerPlugins():
new TaxAppMessageHandlerPlugin(),
```

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

If TaxApp was the only ACP app using the MessageBroker, you can also disable the `message-broker-consume-channels` cronjob in `config/Zed/cronjobs/jenkins.php` and set `MessageBrokerConstants::IS_ENABLED` to `false` in `config/Shared/config_default.php` to stop unnecessary background processing.

{% endinfo_block %}

### 2b. Remove the TaxApp publisher plugin for DMS projects

In `src/Pyz/Zed/Publisher/PublisherDependencyProvider.php`, remove the following import and plugin registration:

```php
// Remove this use statement:
use Spryker\Zed\TaxApp\Communication\Plugin\Publisher\Store\RefreshTaxAppStoreRelationPublisherPlugin;

// In getTaxAppPlugins(), return an empty array:
protected function getTaxAppPlugins(): array
{
    return [];
}
```

### 2c. Replace the TaxApp calculation plugin

In `src/Pyz/Zed/Calculation/CalculationDependencyProvider.php`, replace `TaxAppCalculationPlugin` with `VertexCalculationPlugin` in both quote and order calculator stacks:

```php
// Remove:
use Spryker\Zed\TaxApp\Communication\Plugin\Calculation\TaxAppCalculationPlugin;

// Add:
use SprykerEco\Zed\Vertex\Communication\Plugin\Calculation\VertexCalculationPlugin;
```

In `getQuoteCalculatorPluginStack()` and `getOrderCalculatorPluginStack()`, replace:

```php
// Before:
new TaxAppCalculationPlugin(),

// After:
new VertexCalculationPlugin(),
```

### 2d. Replace the TaxApp OMS plugins

In `src/Pyz/Zed/Oms/OmsDependencyProvider.php`, replace the plugins:

```php
// Remove:
use Spryker\Zed\TaxApp\Communication\Plugin\Oms\Command\SubmitPaymentTaxInvoicePlugin;
use Spryker\Zed\TaxApp\Communication\Plugin\Oms\OrderRefundedEventListenerPlugin;

// Add:
use SprykerEco\Zed\Vertex\Communication\Plugin\Oms\Command\VertexSubmitPaymentTaxInvoicePlugin;
use SprykerEco\Zed\Vertex\Communication\Plugin\Oms\VertexOrderRefundedEventListenerPlugin;
```

In `extendCommandPlugins()`, replace:

```php
// Before:
$commandCollection-&gt;add(new SubmitPaymentTaxInvoicePlugin(), &apos;TaxApp/SubmitPaymentTaxInvoice&apos;);

// After:
$commandCollection-&gt;add(new VertexSubmitPaymentTaxInvoicePlugin(), &apos;Vertex/SubmitPaymentTaxInvoice&apos;);
```

In `getOmsEventTriggeredListenerPlugins()`, replace:

```php
// Before:
new OrderRefundedEventListenerPlugin(),

// After:
new VertexOrderRefundedEventListenerPlugin(),
```

### 2e. Replace the TaxApp Glue Storefront API plugin

In `src/Pyz/Glue/GlueApplication/GlueApplicationDependencyProvider.php`, replace the plugin:

```php
// Remove:
use Spryker\Glue\TaxAppRestApi\Plugin\TaxValidateIdResourceRoutePlugin;

// Add:
use SprykerEco\Glue\Vertex\Plugin\VertexTaxValidateIdResourceRoutePlugin;
```

In `getResourceRoutePlugins()`, replace:

```php
// Before:
new TaxValidateIdResourceRoutePlugin(),

// After:
new VertexTaxValidateIdResourceRoutePlugin(),
```

### 2f. Update OMS state machine XML

Update your OMS process XML files — for example, `config/Zed/oms/DummyPayment01.xml` — to reference the new command name:

```xml
&lt;!-- Before: --&gt;
&lt;event name=&quot;submit tax invoice&quot; onEnter=&quot;true&quot; command=&quot;TaxApp/SubmitPaymentTaxInvoice&quot;/&gt;

&lt;!-- After: --&gt;
&lt;event name=&quot;submit tax invoice&quot; onEnter=&quot;true&quot; command=&quot;Vertex/SubmitPaymentTaxInvoice&quot;/&gt;
```

### 2g. Clean up config_default.php

Remove TaxApp-specific configuration and MessageBroker channel mappings from `config/Shared/config_default.php`:

```php
// Remove these transfer use statements:
use Generated\Shared\Transfer\ConfigureTaxAppTransfer;
use Generated\Shared\Transfer\DeleteTaxAppTransfer;
use Generated\Shared\Transfer\SubmitPaymentTaxInvoiceTransfer;

// Remove this use statement:
use Spryker\Shared\TaxApp\TaxAppConstants;

// Remove TaxAppConstants from OAuth and tenant assignments:
// $config[TaxAppConstants::OAUTH_PROVIDER_NAME] = ...
// $config[TaxAppConstants::OAUTH_GRANT_TYPE] = ...
// $config[TaxAppConstants::OAUTH_OPTION_AUDIENCE] = ...
// $config[TaxAppConstants::TENANT_IDENTIFIER] = ...

// Remove TaxApp MessageBroker channel mappings from $config[MessageBrokerConstants::CHANNEL_TO_RECEIVER_TRANSPORT_MAP]:
// ConfigureTaxAppTransfer::class =&gt; &apos;tax-commands&apos;,
// DeleteTaxAppTransfer::class =&gt; &apos;tax-commands&apos;,
// SubmitPaymentTaxInvoiceTransfer::class =&gt; &apos;payment-tax-invoice-commands&apos;,
```

## 3. Add new Vertex configuration

### 3a. Add Vertex credentials

In `config/Shared/config_default.php`, add the following:

```php
use SprykerEco\Shared\Vertex\VertexConstants;

$config[VertexConstants::IS_ACTIVE] = getenv(&apos;VERTEX_IS_ACTIVE&apos;) ?: null;
$config[VertexConstants::CLIENT_ID] = getenv(&apos;VERTEX_CLIENT_ID&apos;) ?: null;
$config[VertexConstants::CLIENT_SECRET] = getenv(&apos;VERTEX_CLIENT_SECRET&apos;) ?: null;
$config[VertexConstants::SECURITY_URI] = getenv(&apos;VERTEX_SECURITY_URI&apos;) ?: null;
$config[VertexConstants::TRANSACTION_CALLS_URI] = getenv(&apos;VERTEX_TRANSACTION_CALLS_URI&apos;) ?: null;

// Optional: Tax ID Validator (Vertex Validator / Taxamo)
$config[VertexConstants::TAXAMO_API_URL] = getenv(&apos;TAXAMO_API_URL&apos;) ?: null;
$config[VertexConstants::TAXAMO_TOKEN] = getenv(&apos;TAXAMO_TOKEN&apos;) ?: null;
```

### 3b. Override feature flags

Create `src/Pyz/Zed/Vertex/VertexConfig.php`:

```php
&lt;?php

namespace Pyz\Zed\Vertex;

use SprykerEco\Zed\Vertex\VertexConfig as SprykerEcoVertexConfig;

class VertexConfig extends SprykerEcoVertexConfig
{
    public function isTaxIdValidatorEnabled(): bool
    {
        return true;
    }

    public function isTaxAssistEnabled(): bool
    {
        return true;
    }

    public function isInvoicingEnabled(): bool
    {
        return true;
    }
}
```

### 3c. Register expander and fallback plugins

Create `src/Pyz/Zed/Vertex/VertexDependencyProvider.php`:

```php
&lt;?php

namespace Pyz\Zed\Vertex;

use Spryker\Zed\Calculation\Communication\Plugin\Calculator\ItemTaxAmountFullAggregatorPlugin;
use Spryker\Zed\Calculation\Communication\Plugin\Calculator\PriceToPayAggregatorPlugin;
use Spryker\Zed\MerchantProfile\Communication\Plugin\TaxApp\MerchantProfileAddressCalculableObjectTaxAppExpanderPlugin;
use Spryker\Zed\MerchantProfile\Communication\Plugin\TaxApp\MerchantProfileAddressOrderTaxAppExpanderPlugin;
use Spryker\Zed\ProductOfferAvailability\Communication\Plugin\TaxApp\ProductOfferAvailabilityCalculableObjectTaxAppExpanderPlugin;
use Spryker\Zed\ProductOfferAvailability\Communication\Plugin\TaxApp\ProductOfferAvailabilityOrderTaxAppExpanderPlugin;
use Spryker\Zed\Tax\Communication\Plugin\Calculator\TaxAmountAfterCancellationCalculatorPlugin;
use Spryker\Zed\Tax\Communication\Plugin\Calculator\TaxAmountCalculatorPlugin;
use Spryker\Zed\Tax\Communication\Plugin\Calculator\TaxRateAverageAggregatorPlugin;
use SprykerEco\Zed\Vertex\Communication\Plugin\Order\OrderCustomerWithVertexCodeExpanderPlugin;
use SprykerEco\Zed\Vertex\Communication\Plugin\Order\OrderExpensesWithVertexCodeExpanderPlugin;
use SprykerEco\Zed\Vertex\Communication\Plugin\Order\OrderItemProductOptionWithVertexCodeExpanderPlugin;
use SprykerEco\Zed\Vertex\Communication\Plugin\Order\OrderItemWithVertexSpecificFieldsExpanderPlugin;
use SprykerEco\Zed\Vertex\Communication\Plugin\Quote\CalculableObjectCustomerWithVertexCodeExpanderPlugin;
use SprykerEco\Zed\Vertex\Communication\Plugin\Quote\CalculableObjectExpensesWithVertexCodeExpanderPlugin;
use SprykerEco\Zed\Vertex\Communication\Plugin\Quote\CalculableObjectItemProductOptionWithVertexCodeExpanderPlugin;
use SprykerEco\Zed\Vertex\Communication\Plugin\Quote\CalculableObjectItemWithVertexSpecificFieldsExpanderPlugin;
use SprykerEco\Zed\Vertex\VertexDependencyProvider as SprykerVertexDependencyProvider;

class VertexDependencyProvider extends SprykerVertexDependencyProvider
{
    /**
     * @return array&lt;\SprykerEco\Zed\Vertex\Dependency\Plugin\CalculableObjectVertexExpanderPluginInterface|\Spryker\Zed\TaxAppExtension\Dependency\Plugin\CalculableObjectTaxAppExpanderPluginInterface&gt;
     */
    protected function getCalculableObjectVertexExpanderPlugins(): array
    {
        return [
            new CalculableObjectCustomerWithVertexCodeExpanderPlugin(),
            new CalculableObjectExpensesWithVertexCodeExpanderPlugin(),
            new CalculableObjectItemProductOptionWithVertexCodeExpanderPlugin(),
            new CalculableObjectItemWithVertexSpecificFieldsExpanderPlugin(),
            new MerchantProfileAddressCalculableObjectTaxAppExpanderPlugin(),
            new ProductOfferAvailabilityCalculableObjectTaxAppExpanderPlugin(),
        ];
    }

    /**
     * @return array&lt;\SprykerEco\Zed\Vertex\Dependency\Plugin\OrderVertexExpanderPluginInterface|\Spryker\Zed\TaxAppExtension\Dependency\Plugin\OrderTaxAppExpanderPluginInterface&gt;
     */
    protected function getOrderVertexExpanderPlugins(): array
    {
        return [
            new OrderCustomerWithVertexCodeExpanderPlugin(),
            new OrderExpensesWithVertexCodeExpanderPlugin(),
            new OrderItemProductOptionWithVertexCodeExpanderPlugin(),
            new OrderItemWithVertexSpecificFieldsExpanderPlugin(),
            new MerchantProfileAddressOrderTaxAppExpanderPlugin(),
            new ProductOfferAvailabilityOrderTaxAppExpanderPlugin(),
        ];
    }

    /**
     * @return array&lt;\Spryker\Zed\CalculationExtension\Dependency\Plugin\CalculationPluginInterface&gt;
     */
    protected function getFallbackQuoteCalculationPlugins(): array
    {
        return [
            new TaxAmountCalculatorPlugin(),
            new ItemTaxAmountFullAggregatorPlugin(),
            new PriceToPayAggregatorPlugin(),
            new TaxRateAverageAggregatorPlugin(),
        ];
    }

    /**
     * @return array&lt;\Spryker\Zed\CalculationExtension\Dependency\Plugin\CalculationPluginInterface&gt;
     */
    protected function getFallbackOrderCalculationPlugins(): array
    {
        return [
            new TaxAmountCalculatorPlugin(),
            new ItemTaxAmountFullAggregatorPlugin(),
            new PriceToPayAggregatorPlugin(),
            new TaxAmountAfterCancellationCalculatorPlugin(),
        ];
    }
}
```

## 4. Set up the database and transfers

```bash
vendor/bin/console propel:install
vendor/bin/console transfer:generate
```

## 5. Import glossary data

The module provides translation data for tax validation messages.

**Option 1: Import using the module&apos;s configuration file**

```bash
vendor/bin/console data:import --config=vendor/spryker-eco/vertex/data/import/vertex.yml
```

**Option 2: Copy file content and import individually**

Copy content from `vendor/spryker-eco/vertex/data/import/*.csv` to the corresponding files in `data/import/common/common/`. Then run:

```bash
vendor/bin/console data:import glossary
```

**Option 3: Add to the project&apos;s main import configuration**

Add the import actions to your project&apos;s main data import configuration file and include them in your regular import pipeline.

## 6. Verify the migration

1. Clear caches: `vendor/bin/console cache:empty-all`.
2. Place a test order and verify that tax calculation works.
3. If invoicing is enabled, verify that the `Vertex/SubmitPaymentTaxInvoice` OMS command triggers correctly.
4. If tax ID validation is enabled, test the `POST /tax-id-validate` Glue Storefront API endpoint.

For detailed verification steps, see [Verify Vertex connection](/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/verify-vertex-connection.html).

## Summary of changes

| Component | ACP (before) | ECO (after) |
|-----------|-------------|-------------|
| Tax calculation plugin | `TaxAppCalculationPlugin` | `VertexCalculationPlugin` |
| OMS invoice command | `SubmitPaymentTaxInvoicePlugin` (`TaxApp/...`) | `VertexSubmitPaymentTaxInvoicePlugin` (`Vertex/...`) |
| OMS refund listener | `OrderRefundedEventListenerPlugin` | `VertexOrderRefundedEventListenerPlugin` |
| Glue tax validation | `TaxValidateIdResourceRoutePlugin` | `VertexTaxValidateIdResourceRoutePlugin` |
| MessageBroker handler | `TaxAppMessageHandlerPlugin` | Removed (not needed) |
| Publisher plugin | `RefreshTaxAppStoreRelationPublisherPlugin` | Removed (not needed) |
| Configuration | `TaxAppConstants` + OAuth | `VertexConstants` + direct API credentials |
| Communication | Via MessageBroker (async) | Direct Vertex API calls (sync) |
</description>
            <pubDate>Wed, 25 Mar 2026 12:51:03 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/install-vertex/migrate-from-acp-to-vertex.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/install-vertex/migrate-from-acp-to-vertex.html</guid>
            
            
        </item>
        
        <item>
            <title>Performance guidelines</title>
            <description>These performance guidelines originate from Spryker&apos;s years of experience across all kinds of projects, environments, and setups. They cover topics that are often missed at the project level, leading to poor performance or other related issues. The guidelines help you analyze and optimize performance of your website from different perspectives:

- [Keeping dependencies updated](/docs/dg/dev/guidelines/performance-guidelines/keeping-dependencies-updated.html) to maintain optimal performance and security by staying current with Spryker module updates.
- [Monitoring](/docs/dg/dev/guidelines/performance-guidelines/monitoring.html) to ensure effective application monitoring using APM tools.
- [General performance guidelines](/docs/dg/dev/guidelines/performance-guidelines/general-performance-guidelines.html) for general approaches to optimizing the server-side execution time.
- [Architecture performance guidelines](/docs/dg/dev/guidelines/performance-guidelines/architecture-performance-guidelines.html) to optimize performance in the very end servers.
- [Frontend performance guidelines](/docs/dg/dev/guidelines/performance-guidelines/front-end-performance-guidelines.html) to do the frontend-specific optimization.
- [Twig performance best practices](/docs/dg/dev/guidelines/performance-guidelines/twig-performance-best-practices.html) to optimize Twig templating engine performance.
- [Session locks](/docs/dg/dev/guidelines/performance-guidelines/session-locks.html) to optimize session locking mechanisms and reduce performance impact. Also see [Redis session lock](/docs/dg/dev/troubleshooting/troubleshooting-performance-issues/redis-session-lock.html) for troubleshooting session lock issues.
- [Bot control](/docs/dg/dev/guidelines/performance-guidelines/bot-control.html) to manage honest and malicious bot traffic effectively.
- [Batch processing of Propel entities](/docs/dg/dev/guidelines/performance-guidelines/performance-guidelines-batch-processing-propel-entities.html) for efficient batch processing, reduced database load, and support for complex entity relationships.
- [Database performance guidelines](/docs/dg/dev/guidelines/performance-guidelines/database-performance-guidelines.html) to optimize database operations through proper indexing, query optimization, and avoiding common database anti-patterns.
- [Key-Value storage performance guidelines](/docs/dg/dev/guidelines/performance-guidelines/key-value-storage-performance-guidelines.html) to optimize Redis/ValKey usage by limiting operations, avoiding admin commands in runtime, and implementing proper caching strategies.
- [External HTTP requests](/docs/dg/dev/guidelines/performance-guidelines/external-http-requests.html) to understand Spryker&apos;s architecture principle of reading from fast storage and learn how to manage external HTTP requests when they&apos;re necessary.
- [Search performance guidelines](/docs/dg/dev/guidelines/performance-guidelines/search-performance-guidelines.html) to optimize Elasticsearch and OpenSearch performance, avoid common search anti-patterns, and implement efficient search queries.
- [Infrastructure and worker configuration guidelines](/docs/dg/dev/guidelines/performance-guidelines/infrastructure-worker-configuration-guidelines.html) to optimize nginx configuration and worker orchestration for multi-store setups.
- [CDN and traffic management integration](/docs/dg/dev/guidelines/performance-guidelines/cdn-and-traffic-management-integration.html) to configure CDN solutions like Akamai or Cloudflare to work correctly with Spryker&apos;s nginx compression and avoid unnecessary data transfer overhead.
- [Custom code performance guidelines](/docs/dg/dev/guidelines/performance-guidelines/custom-code-performance-guidelines.html) to implement performant custom code including caching strategies, background processing, and Quote calculator optimization.
- [Common pitfalls in OMS design](/docs/pbc/all/order-management-system/latest/base-shop/datapayload-conversion/state-machine/common-pitfalls-in-oms-design.html) to avoid performance bottlenecks in Order Management System processes, especially ensuring heavy operations run in CLI context rather than during web requests.
- [Order management system multi-thread](/docs/pbc/all/order-management-system/latest/base-shop/datapayload-conversion/state-machine/order-management-system-multi-thread.html) to process OMS timeouts and conditions in parallel for improved order processing performance.
- [Troubleshooting performance issues](/docs/dg/dev/troubleshooting/troubleshooting-performance-issues/troubleshooting-performance-issues.html) for detecting and fixing common performance problems.
- [Order details page performance guidance](/docs/pbc/all/order-management-system/latest/base-shop/order-management-feature-overview/order-details-page-performance-overview.html) to optimize the order detail page in the Back Office
</description>
            <pubDate>Wed, 25 Mar 2026 12:42:04 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/guidelines/performance-guidelines/performance-guidelines.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/guidelines/performance-guidelines/performance-guidelines.html</guid>
            
            
        </item>
        
        <item>
            <title>Order details page performance guidance</title>
            <description>The Back Office order details page can contain a large number of sub-requests. While sub-requests are a valid architectural pattern, in high-traffic environments they can become a source of performance issues—each sub-request adds latency and load to the system.

To address this, Spryker introduces two plugin stacks that replace sub-requests with direct plugin-based data expansion and rendering on the order details page.

## How it works

Previously, the order details page loaded data and rendered blocks by making sub-requests to other modules. Each sub-request was an independent HTTP call processed by the application stack, which multiplied response time and resource consumption proportionally to the number of blocks on the page.

The new approach uses two plugin stacks:

1. **Data expansion plugins**: Plugins collect and provide data directly, bypassing sub-requests.
2. **Block renderer plugins**: Plugins render template blocks directly, bypassing sub-requests.

If a plugin is registered, its data or rendered output is used. If a plugin is not registered, the system falls back to the original sub-request behavior, ensuring backward compatibility.

## Install the feature

To install the feature, update the required modules using Composer. The following is the full list of modules involved—your project may not require all of them.

```bash
composer update spryker/gift-card:&quot;^1.13.0&quot; spryker/manual-order-entry:&quot;^1.3.0&quot; spryker/money:&quot;^2.17.0&quot; spryker/oms:&quot;^11.52.0&quot; spryker/refund:&quot;^5.15.0&quot; spryker/sales:&quot;^11.81.0&quot; spryker/sales-extension:&quot;^1.14.0&quot; spryker/sales-order-threshold-gui:&quot;^2.2.0&quot; spryker/sales-payment-detail:&quot;^1.6.0&quot; spryker/sales-product-configuration-gui:&quot;^1.1.0&quot; spryker/sales-return-gui:&quot;^2.2.0&quot; spryker/shipment-gui:&quot;^3.2.0&quot; spryker-feature/self-service-portal:&quot;^19.4.0&quot; spryker/merchant-user:&quot;^1.9.1&quot;
```

For a full integration example, see the [B2B Demo Marketplace integration PR](https://github.com/spryker-shop/b2b-demo-marketplace/pull/957/changes).

For the complete release details, see [Release Group 6396](https://api.release.spryker.com/release-group/6396).

## Plugin stacks

### Data expansion plugins

The `getSalesOrderDetailDataExpanderPlugins()` method in `SalesDependencyProvider` registers plugins that expand order detail data directly, without sub-requests.

**Location:** `src/Pyz/Zed/Sales/SalesDependencyProvider.php`

```php
/**
 * @return array&lt;\Spryker\Zed\SalesExtension\Dependency\Plugin\SalesOrderDetailDataExpanderPluginInterface&gt;
 */
protected function getSalesOrderDetailDataExpanderPlugins(): array
{
    return [
        new ShipmentSalesOrderDetailDataExpanderPlugin(),
        new ProductConfigurationSalesOrderDetailDataExpanderPlugin(),
        new CommentsSalesOrderDetailDataExpanderPlugin(),
        new OmsFormsSalesOrderDetailDataExpanderPlugin(),
        new ThresholdExpensesSalesOrderDetailDataExpanderPlugin(),
        new ShipmentExpensesSalesOrderDetailDataExpanderPlugin(),
    ];
}
```

{% info_block infoBox &quot;Plugin registration&quot; %}

Register only the plugins that are relevant to your project. Not all plugins may be required depending on the modules you use.

{% endinfo_block %}

### Block renderer plugins

The `getSalesDetailBlockRendererPlugins()` method in `SalesDependencyProvider` registers plugins that render order detail blocks directly, without sub-requests.

**Location:** `src/Pyz/Zed/Sales/SalesDependencyProvider.php`

```php
/**
 * @return array&lt;\Spryker\Zed\SalesExtension\Dependency\Plugin\SalesDetailBlockRendererPluginInterface&gt;
 */
protected function getSalesDetailBlockRendererPlugins(): array
{
    return [
        new OrderTransferBlockRendererPlugin(),
        new SalesCommentBlockRendererPlugin(),
        new SalesReturnListBlockRendererPlugin(),
        new SalesPaymentDetailListBlockRendererPlugin(),
        new RefundSalesListBlockRendererPlugin(),
        new SelfServicePortalOrderInquiryListBlockRendererPlugin(),
    ];
}
```

{% info_block infoBox &quot;Plugin registration&quot; %}

Register only the plugins that are relevant to your project. Not all plugins may be required depending on the modules you use.

{% endinfo_block %}

This plugin stack replaces the sub-request configuration defined in `\Pyz\Zed\Sales\SalesConfig::getSalesDetailExternalBlocksUrls`.

### OrderTransferBlockRendererPlugin template map

`OrderTransferBlockRendererPlugin` requires an additional configuration on the project level that maps block URLs to Twig templates.

**Location:** `src/Pyz/Zed/Sales/SalesConfig.php`

```php
/**
 * @return array&lt;string, string&gt;
 */
public function getOrderDetailBlockUrlToTemplateMap(): array
{
    return [
        &apos;/cart-note/sales/list&apos; =&gt; &apos;@CartNote/Sales/list.twig&apos;,
        &apos;/comment-sales-connector/sales/list&apos; =&gt; &apos;@CommentSalesConnector/Sales/list.twig&apos;,
        &apos;/cart-note-product-bundle-connector/sales/list&apos; =&gt; &apos;@CartNoteProductBundleConnector/Sales/list.twig&apos;,
        &apos;/sales-payment-gui/sales/list&apos; =&gt; &apos;@SalesPaymentGui/Sales/list.twig&apos;,
        &apos;/discount/sales/list&apos; =&gt; &apos;@Discount/Sales/list.twig&apos;,
    ];
}
```

Include only the entries that correspond to the modules enabled in your project.
</description>
            <pubDate>Wed, 25 Mar 2026 12:35:52 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/order-management-system/latest/base-shop/order-management-feature-overview/order-details-page-performance-overview.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/order-management-system/latest/base-shop/order-management-feature-overview/order-details-page-performance-overview.html</guid>
            
            
        </item>
        
        <item>
            <title>Integrate Vertex</title>
            <description>This document describes how to integrate [Vertex](/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/vertex.html) into a Spryker shop.

## Prerequisites

Before integrating Vertex, ensure the following prerequisites are met:

- Make sure that your deployment pipeline executes database migrations.

## 1. Install the module

Install the Vertex module using Composer:

```bash
composer require spryker-eco/vertex
```

## 2. Configure the module

Add the following configuration to `config/Shared/config_default.php`:

```php
use SprykerEco\Shared\Vertex\VertexConstants;

$config[VertexConstants::IS_ACTIVE] = getenv(&apos;VERTEX_IS_ACTIVE&apos;);
$config[VertexConstants::CLIENT_ID] = getenv(&apos;VERTEX_CLIENT_ID&apos;);
$config[VertexConstants::CLIENT_SECRET] = getenv(&apos;VERTEX_CLIENT_SECRET&apos;);
$config[VertexConstants::SECURITY_URI] = getenv(&apos;VERTEX_SECURITY_URI&apos;);
$config[VertexConstants::TRANSACTION_CALLS_URI] = getenv(&apos;VERTEX_TRANSACTION_CALLS_URI&apos;);
// Optional: Tax ID Validator (requires Vertex Validator, previously known as Taxamo, see https://developer.vertexinc.com/vertex-e-commerce/docs/stand-alone-deployments)
$config[VertexConstants::TAXAMO_API_URL] = getenv(&apos;TAXAMO_API_URL&apos;);
$config[VertexConstants::TAXAMO_TOKEN] = getenv(&apos;TAXAMO_TOKEN&apos;);

// Optional: Vendor Code
$config[VertexConstants::VENDOR_CODE] = &apos;&apos;;
```

### Required configuration constants

| Constant | Description | Where to get the value |
|----------|-------------|------------------------|
| `IS_ACTIVE` | Enables or disables Vertex tax calculation. | Set to `true` to enable. |
| `CLIENT_ID` | OAuth client ID for the Vertex API. | Obtain from your Vertex account. For details, see [Vertex documentation](https://tax-calc-api.vertexcloud.com/resources/index.html). |
| `CLIENT_SECRET` | OAuth client secret for the Vertex API. | Obtain from your Vertex account. For details, see [Vertex documentation](https://tax-calc-api.vertexcloud.com/resources/index.html). |
| `SECURITY_URI` | Vertex OAuth security endpoint. | Obtain from your Vertex platform. For details, see [Vertex documentation](https://tax-calc-api.vertexcloud.com/resources/index.html). |
| `TRANSACTION_CALLS_URI` | Vertex transaction calls endpoint. | Obtain from your Vertex platform. For details, see [Vertex documentation](https://tax-calc-api.vertexcloud.com/resources/index.html). |

### Optional configuration constants

| Constant | Description | Where to get the value |
|----------|-------------|------------------------|
| `TAXAMO_API_URL` | Vertex Validator API URL for tax ID validation. | Obtain from your Vertex Validator environment. For details, see [Standalone Vertex Validator](https://developer.vertexinc.com/vertex-e-commerce/docs/stand-alone-deployments). |
| `TAXAMO_TOKEN` | Vertex Validator API authentication token. | Obtain from your Vertex Validator account. For details, see [Accessing the APIs](https://developer.vertexinc.com/vertex-marketplaces/docs/getting-started-1). |
| `VENDOR_CODE` | Vendor code for Vertex tax calculations. | Set in your Vertex account. |
| `DEFAULT_TAXPAYER_COMPANY_CODE` | Default taxpayer company code. | The company code you set in your Vertex account. |

## 3. Override feature flags

The `isTaxIdValidatorEnabled`, `isTaxAssistEnabled`, and `isInvoicingEnabled` methods default to `false` and are not driven by constants. To enable them, override `src/Pyz/Zed/Vertex/VertexConfig.php`:

```php
namespace Pyz\Zed\Vertex;

use SprykerEco\Zed\Vertex\VertexConfig as SprykerEcoVertexConfig;

class VertexConfig extends SprykerEcoVertexConfig
{
    public function isTaxIdValidatorEnabled(): bool
    {
        return true;
    }

    public function isTaxAssistEnabled(): bool
    {
        return true;
    }

    public function isInvoicingEnabled(): bool
    {
        return true;
    }
}
```

### Config methods

The following methods must be overridden in `src/Pyz/Zed/Vertex/VertexConfig.php` to enable the respective features:

| Method | Default | Description                                                                                                                                                                       |
|--------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `isTaxIdValidatorEnabled()` | `false` | Enables tax ID validation via [Vertex Validator](https://developer.vertexinc.com/vertex-e-commerce/docs/stand-alone-deployments). Requires `TAXAMO_API_URL` and `TAXAMO_TOKEN` to be set.                                                                                     |
| `isTaxAssistEnabled()` | `false` | Enables the tax assist feature. Return Assisted Parameters in the response that will provide more details about the calculation. The logs can be checked in the Vertex Dashboard. |
| `isInvoicingEnabled()` | `false` | Enables invoicing functionality. Requires OMS plugins to be registered. See [Register OMS plugins](#register-oms-plugins).                                                        |
| `getSellerCountryCode()` | `&apos;&apos;` | Overrides the default seller country code (2-letter ISO code, for example, `US`). Defaults to the first country of the store.                                                     |
| `getCustomerCountryCode()` | `&apos;&apos;` | Overrides the default customer country code (applied only when no customer billing address is provided).  Defaults to the first country of the store.                             |

## 4. Set up the database schema

Install the database schema:

```bash
vendor/bin/console propel:install
```

## 5. Generate transfer objects

Generate transfer objects for the module:

```bash
vendor/bin/console transfer:generate
```

## 6. Register plugins

### Register the tax calculation plugin

Add the Vertex calculation plugin to `src/Pyz/Zed/Calculation/CalculationDependencyProvider.php`:

```php
use SprykerEco\Zed\Vertex\Communication\Plugin\Calculation\VertexCalculationPlugin;

protected function getQuoteCalculatorPluginStack(Container $container): array
{
    return [
        //...

        # Suggested plugins order is shown.

        new ItemDiscountAmountFullAggregatorPlugin(),

        # This plugin is replacing other tax calculation plugins in the stack and will use them as a fallback.
        # No other tax calculation plugins except for VertexCalculationPlugin should be present in the stack.
        new VertexCalculationPlugin(),

        new PriceToPayAggregatorPlugin(),

        //...
    ];
}

protected function getOrderCalculatorPluginStack(Container $container): array
{
    return [
        //...

        # Suggested plugins order is shown.

        new ItemDiscountAmountFullAggregatorPlugin(),

        # This plugin is replacing other tax calculation plugins in the stack and will use them as a fallback.
        # No other tax calculation plugins except for VertexCalculationPlugin should be present in the stack.
        new VertexCalculationPlugin(),

        new PriceToPayAggregatorPlugin(),

        //...
    ];
}
```

#### Register Fallback Calculation Plugins

Add order and quote Fallback Calculation Plugins to `src/Pyz/Zed/Vertex/VertexDependencyProvider.php`:

```php
use Spryker\Zed\Calculation\Communication\Plugin\Calculator\ItemTaxAmountFullAggregatorPlugin;
use Spryker\Zed\Calculation\Communication\Plugin\Calculator\PriceToPayAggregatorPlugin;
use Spryker\Zed\Tax\Communication\Plugin\Calculator\TaxAmountAfterCancellationCalculatorPlugin;
use Spryker\Zed\Tax\Communication\Plugin\Calculator\TaxAmountCalculatorPlugin;
use Spryker\Zed\Tax\Communication\Plugin\Calculator\TaxRateAverageAggregatorPlugin;

/**
 * {@inheritDoc}
 *
 * @return array&lt;\Spryker\Zed\CalculationExtension\Dependency\Plugin\CalculationPluginInterface&gt;
 */
protected function getFallbackQuoteCalculationPlugins(): array
{
    return [
        # These plugins will be called if Vertex configuration is missing or Vertex is disabled.
        # Please note that this list includes PriceToPayAggregatorPlugin - this plugin isn&apos;t a part of tax calculation logic but it&apos;s required by TaxRateAverageAggregatorPlugin.
        new TaxAmountCalculatorPlugin(),
        new ItemTaxAmountFullAggregatorPlugin(),
        new PriceToPayAggregatorPlugin(),
        new TaxRateAverageAggregatorPlugin(),
    ];
}

/**
 * {@inheritDoc}
 *
 * @return array&lt;\Spryker\Zed\CalculationExtension\Dependency\Plugin\CalculationPluginInterface&gt;
 */
protected function getFallbackOrderCalculationPlugins(): array
{
    return [
        # These plugins will be called if Vertex configuration is missing or Vertex is disabled.
        # Please note that this list includes PriceToPayAggregatorPlugin - this plugin isn&apos;t a part of tax calculation logic but it&apos;s required by TaxAmountAfterCancellationCalculatorPlugin.
        new TaxAmountCalculatorPlugin(),
        new ItemTaxAmountFullAggregatorPlugin(),
        new PriceToPayAggregatorPlugin(),
        new TaxAmountAfterCancellationCalculatorPlugin(),
    ];
}
```

In general, `getFallbackQuoteCalculationPlugins()` and `getFallbackOrderCalculationPlugins()` methods should contain the tax calculation plugins, which are replaced by `VertexCalculationPlugin` in `\Pyz\Zed\Calculation\CalculationDependencyProvider`.
The code snippet above is an example of such configuration based on the Spryker default tax calculation plugins.
Tax calculation plugins moved:
- from `getQuoteCalculatorPluginStack` method: `TaxAmountCalculatorPlugin`, `ItemTaxAmountFullAggregatorPlugin`, `PriceToPayAggregatorPlugin`, `TaxRateAverageAggregatorPlugin`
- from `getOrderCalculatorPluginStack` method: `TaxAmountCalculatorPlugin`, `ItemTaxAmountFullAggregatorPlugin`, `PriceToPayAggregatorPlugin`, `TaxAmountAfterCancellationCalculatorPlugin`

{% info_block infoBox &quot;Fallback behavior&quot; %}

There are three different failure scenarios where `VertexCalculationPlugin` might need to use a fallback logic:

1. Vertex isn&apos;t connected: fallback plugins defined in `getFallbackQuoteCalculationPlugins()` and `getFallbackOrderCalculationPlugins()` will be used to calculate taxes.
2. Vertex is disabled: fallback plugins defined in `getFallbackQuoteCalculationPlugins()` and `getFallbackOrderCalculationPlugins()` will be used to calculate taxes.
3. Vertex is not responding or is responding with an error: tax value will be set to zero, and the customer will be able to proceed with the checkout.

{% endinfo_block %}

### Register CalculableObject and order expander plugins

Add order and CalculableObject expander plugins to `src/Pyz/Zed/Vertex/VertexDependencyProvider.php`. The proposed plugins are examples, you can select which ones to register based on your requirements or create custom ones if needed.

```php
use SprykerEco\Zed\Vertex\Communication\Plugin\Order\OrderCustomerWithVertexCodeExpanderPlugin;
use SprykerEco\Zed\Vertex\Communication\Plugin\Order\OrderExpensesWithVertexCodeExpanderPlugin;
use SprykerEco\Zed\Vertex\Communication\Plugin\Order\OrderItemProductOptionWithVertexCodeExpanderPlugin;
use SprykerEco\Zed\Vertex\Communication\Plugin\Order\OrderItemWithVertexSpecificFieldsExpanderPlugin;
use SprykerEco\Zed\Vertex\Communication\Plugin\Quote\CalculableObjectCustomerWithVertexCodeExpanderPlugin;
use SprykerEco\Zed\Vertex\Communication\Plugin\Quote\CalculableObjectExpensesWithVertexCodeExpanderPlugin;
use SprykerEco\Zed\Vertex\Communication\Plugin\Quote\CalculableObjectItemProductOptionWithVertexCodeExpanderPlugin;
use SprykerEco\Zed\Vertex\Communication\Plugin\Quote\CalculableObjectItemWithVertexSpecificFieldsExpanderPlugin;

protected function getCalculableObjectVertexExpanderPlugins(): array
{
    return [
        // ... other plugins
        new CalculableObjectCustomerWithVertexCodeExpanderPlugin(),
        new CalculableObjectExpensesWithVertexCodeExpanderPlugin(),
        new CalculableObjectItemProductOptionWithVertexCodeExpanderPlugin(),
        new CalculableObjectItemWithVertexSpecificFieldsExpanderPlugin(),
    ];
}

protected function getOrderVertexExpanderPlugins(): array
{
    return [
        // ... other plugins
        new OrderCustomerWithVertexCodeExpanderPlugin(),
        new OrderExpensesWithVertexCodeExpanderPlugin(),
        new OrderItemProductOptionWithVertexCodeExpanderPlugin(),
        new OrderItemWithVertexSpecificFieldsExpanderPlugin(),
    ];
}
```

## 7. Configure the Shop Application dependency provider

Add the following code to `src/Pyz/Yves/ShopApplication/ShopApplicationDependencyProvider.php`:

```php

namespace Pyz\Yves\ShopApplication;

use SprykerShop\Yves\ShopApplication\ShopApplicationDependencyProvider as SprykerShopApplicationDependencyProvider;
use SprykerShop\Yves\CartPage\Widget\CartSummaryHideTaxAmountWidget;

class ShopApplicationDependencyProvider extends SprykerShopApplicationDependencyProvider
{
    /**
     * @phpstan-return array&lt;class-string&lt;\Spryker\Yves\Kernel\Widget\AbstractWidget&gt;&gt;
     *
     * @return array&lt;string&gt;
     */
    protected function getGlobalWidgets(): array
    {
        return [
            //...

            # This widget is replacing Spryker default tax display in cart summary page with text stating that tax amount will be calculated during checkout process.
            CartSummaryHideTaxAmountWidget::class,
        ];
    }
}

```

If you have custom Yves templates or make your own Frontend, add `CartSummaryHideTaxAmountWidget` to your template. The core template is located at `SprykerShop/Yves/CartPage/Theme/default/components/molecules/cart-summary/cart-summary.twig`.

Here is an example with `CartSummaryHideTaxAmountWidget`:

```html
{% raw %}
&lt;li class=&quot;list__item spacing-y&quot;&gt;
    {{ &apos;cart.total.tax_total&apos; | trans }}
    {% widget &apos;CartSummaryHideTaxAmountWidget&apos; args [data.cart] only %}
    {% nowidget %}
        &lt;span class=&quot;float-right&quot;&gt;{{ data.cart.totals.taxTotal.amount | money(true, data.cart.currency.code) }}&lt;/span&gt;
    {% endwidget %}
&lt;/li&gt;
{% endraw %}
```

## 8. Optional: Sending tax invoices to Vertex and handling refunds

Configure payment `config/Zed/oms/{your_payment_oms}.xml`as in the following example:

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

    &lt;process name=&quot;SomePaymentProcess&quot; main=&quot;true&quot;&gt;

        &lt;!-- other configurations --&gt;

        &lt;states&gt;

            &lt;!-- other states --&gt;

            &lt;state name=&quot;tax invoice submitted&quot; reserved=&quot;true&quot; display=&quot;oms.state.paid&quot;/&gt;

            &lt;!-- other states --&gt;

        &lt;/states&gt;

        &lt;transitions&gt;

            &lt;!-- other transitions --&gt;

            &lt;transition happy=&quot;true&quot;&gt;
                &lt;source&gt;paid&lt;/source&gt; &lt;!-- Suggested that paid transition should be the source, but it&apos;s up to you --&gt;
                &lt;target&gt;tax invoice submitted&lt;/target&gt;
                &lt;event&gt;submit tax invoice&lt;/event&gt;
            &lt;/transition&gt;

            &lt;!-- other transitions --&gt;

            &lt;transition happy=&quot;true&quot;&gt;
                &lt;source&gt;tax invoice submitted&lt;/source&gt;

                &lt;!-- Here are the contents of the target transition --&gt;

            &lt;/transition&gt;

            &lt;!-- other transitions --&gt;

        &lt;/transitions&gt;

        &lt;events&gt;

            &lt;!-- other events --&gt;

            &lt;event name=&quot;submit tax invoice&quot; onEnter=&quot;true&quot; command=&quot;Vertex/SubmitPaymentTaxInvoice&quot;/&gt;

            &lt;!-- other events --&gt;

        &lt;/events&gt;

    &lt;/process&gt;

&lt;/statemachine&gt;
```

### Register OMS plugins

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

This step is required only if you want to use invoicing functionality. Make sure `isInvoicingEnabled()` is set to `true` in `VertexConfig.php`.

{% endinfo_block %}

Add OMS plugins to `src/Pyz/Zed/Oms/OmsDependencyProvider.php`:

```php
use SprykerEco\Zed\Vertex\Communication\Plugin\Oms\Command\VertexSubmitPaymentTaxInvoicePlugin;
use SprykerEco\Zed\Vertex\Communication\Plugin\Oms\VertexOrderRefundedEventListenerPlugin;

# This configuration is necessary for Invoice functionality
protected function extendCommandPlugins(Container $container): Container
{
    $container-&gt;extend(self::COMMAND_PLUGINS, function (CommandCollectionInterface $commandCollection) {
        // ... other command plugins
        $commandCollection-&gt;add(new VertexSubmitPaymentTaxInvoicePlugin(), &apos;Vertex/SubmitPaymentTaxInvoice&apos;);

        return $commandCollection;
    });

    return $container;
}

# This configuration is necessary for Refund functionality
protected function getOmsEventTriggeredListenerPlugins(Container $container): array
{
    return [
        // ... other plugins
        new VertexOrderRefundedEventListenerPlugin(),
    ];
}
```

This configuration of `getOmsEventTriggeredListenerPlugins` method is required to ensure that the correct tax amount will be used during the refund process.

{% info_block infoBox &quot;OMS configuration requirement&quot; %}

The refund functionality will only work if the OMS event is called `refund`.

{% endinfo_block %}

## Next steps

- [Configure Vertex-specific metadata](/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/install-vertex/configure-vertex-specific-metadata.html)
- [Migrate from the ACP Vertex app](/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/install-vertex/migrate-from-acp-to-vertex.html)</description>
            <pubDate>Wed, 25 Mar 2026 12:21:38 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/install-vertex/integrate-vertex.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/install-vertex/integrate-vertex.html</guid>
            
            
        </item>
        
        <item>
            <title>AI Foundation Audit Logs</title>
            <description>&lt;p&gt;This document describes how to use audit logging with the AiFoundation module to track and audit AI interactions in your Spryker application.&lt;/p&gt;
&lt;p&gt;The audit logging feature provides comprehensive tracking of all AI interactions, including prompts, responses, token usage, inference time, and metadata. This enables monitoring, compliance, cost tracking, and debugging of AI operations.&lt;/p&gt;
&lt;h2 id=&quot;back-office-audit-logs-page&quot;&gt;Back Office: Audit Logs page&lt;/h2&gt;
&lt;p&gt;After &lt;a href=&quot;#enable-audit-logging&quot;&gt;enabling audit logging&lt;/a&gt;, you can view and analyze AI interaction logs in the Back Office at &lt;strong&gt;Intelligence &amp;gt; Audit Logs&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The Audit Logs page provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Summary statistics cards&lt;/strong&gt;: Total requests, total tokens consumed, success rate, and average inference time for the filtered dataset.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Filterable data table&lt;/strong&gt;: Filter logs by configuration name, status (success/failed), conversation reference, and date range.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Detail drawer&lt;/strong&gt;: Click a row’s prompt to view complete prompt and response text, token breakdown, metadata, and error details.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;navigation-setup&quot;&gt;Navigation setup&lt;/h3&gt;
&lt;p&gt;The AiFoundation module registers its navigation under the &lt;strong&gt;Intelligence&lt;/strong&gt; menu. To include the Audit Logs entry in your project navigation, add the following to &lt;code&gt;config/Zed/navigation.xml&lt;/code&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;ai-foundation&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Intelligence&lt;span class=&quot;nt&quot;&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Intelligence&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;icon&amp;gt;&lt;/span&gt;network_intel_node&lt;span class=&quot;nt&quot;&gt;&amp;lt;/icon&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;pages&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;ai-interaction-log&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;label&amp;gt;&lt;/span&gt;Audit Logs&lt;span class=&quot;nt&quot;&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Audit Logs&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;bundle&amp;gt;&lt;/span&gt;ai-foundation&lt;span class=&quot;nt&quot;&gt;&amp;lt;/bundle&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;controller&amp;gt;&lt;/span&gt;ai-interaction-log&lt;span class=&quot;nt&quot;&gt;&amp;lt;/controller&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;action&amp;gt;&lt;/span&gt;index&lt;span class=&quot;nt&quot;&gt;&amp;lt;/action&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/ai-interaction-log&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/pages&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/ai-foundation&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;After updating the navigation XML, rebuild the navigation cache:&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;console navigation:cache:remove
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;
&lt;p&gt;The AiFoundation audit logging system automatically captures detailed information about each AI interaction:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Configuration and Provider Information&lt;/strong&gt;: The AI configuration name, provider, and model used&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prompt and Response Data&lt;/strong&gt;: The user prompt and AI response messages&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token Usage&lt;/strong&gt;: Input and output token counts for cost tracking&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance Metrics&lt;/strong&gt;: Inference time in milliseconds&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metadata&lt;/strong&gt;: Tool invocations, errors, and structured schema information&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Conversation Tracking&lt;/strong&gt;: Links to conversation references for multi-turn conversations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Status&lt;/strong&gt;: Success or failure status of each interaction&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timestamps&lt;/strong&gt;: Creation timestamps for each logged interaction&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All AI interactions are persisted to the &lt;code&gt;spy_ai_interaction_log&lt;/code&gt; database table and can be queried, analyzed, and audited.&lt;/p&gt;
&lt;h2 id=&quot;architecture&quot;&gt;Architecture&lt;/h2&gt;
&lt;p&gt;The audit logging system uses post-prompt plugins to capture AI interaction data and integrates with the Spryker logging infrastructure. For detailed architecture information, see &lt;a href=&quot;/docs/dg/dev/ai/ai-foundation/ai-foundation-module.html&quot;&gt;AiFoundation module Overview&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;enable-audit-logging&quot;&gt;Enable audit logging&lt;/h2&gt;
&lt;h3 id=&quot;register-the-audit-logger-plugins&quot;&gt;1. Register the audit logger plugins&lt;/h3&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;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\AiFoundation&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\AiFoundation\AiFoundationDependencyProvider&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SprykerAiFoundationDependencyProvider&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\AiFoundation\Communication\Plugin\AuditLogPostPromptPlugin&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\AiFoundation\Communication\Plugin\AuditLogPostToolCallPlugin&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\AiFoundation\Communication\Plugin\Log\AiInteractionHandlerPlugin&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;AiFoundationDependencyProvider&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SprykerAiFoundationDependencyProvider&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\AiFoundation\Dependency\Plugin\PostPromptPluginInterface&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;getPostPromptPlugins&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;AuditLogPostPromptPlugin&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;cd&quot;&gt;/**
     * @return array&amp;lt;\Spryker\Zed\AiFoundation\Dependency\Plugin\PostToolCallPluginInterface&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;getPostToolCallPlugins&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;AuditLogPostToolCallPlugin&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;cd&quot;&gt;/**
     * @return array&amp;lt;\Spryker\Shared\Log\Dependency\Plugin\LogHandlerPluginInterface&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;getAiInteractionLogHandlerPlugins&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;AiInteractionHandlerPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;configure-the-audit-logger-in-your-config&quot;&gt;2. Configure the audit logger in your config&lt;/h3&gt;
&lt;p&gt;In your configuration file (for example, &lt;code&gt;config/Shared/config_default.php&lt;/code&gt;), add the &lt;code&gt;AiInteractionAuditLoggerConfigPlugin&lt;/code&gt; to the audit logger plugins:&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;use&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Spryker\Shared\Log\LogConstants&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\AiFoundation\Communication\Plugin\Log\AiInteractionAuditLoggerConfigPlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LogConstants&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AUDIT_LOGGER_CONFIG_PLUGINS_ZED&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;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// existing plugins...&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;AiInteractionAuditLoggerConfigPlugin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LogConstants&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;AUDIT_LOGGER_CONFIG_PLUGINS_MERCHANT_PORTAL&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;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// existing plugins...&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;AiInteractionAuditLoggerConfigPlugin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;generate-transfers&quot;&gt;3. Generate transfers&lt;/h3&gt;
&lt;p&gt;Generate the transfer objects for audit logging:&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;console transfer:generate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id=&quot;run-database-migrations&quot;&gt;4. Run database migrations&lt;/h3&gt;
&lt;p&gt;Run the database migrations to create the &lt;code&gt;spy_ai_interaction_log&lt;/code&gt; table:&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;console propel:install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;understanding-ai-interaction-log-data&quot;&gt;Understanding AI interaction log data&lt;/h2&gt;
&lt;h3 id=&quot;aiinteractionlog-transfer-properties&quot;&gt;AiInteractionLog transfer properties&lt;/h3&gt;
&lt;p&gt;Each logged AI interaction contains the following information:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;idAiInteractionLog&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;Unique identifier for the log record&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;configurationName&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;AI configuration name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;provider&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;AI provider name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;AI model name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prompt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;User prompt sent to the AI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;response&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;AI response message&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inputTokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;Input token count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;outputTokens&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;Output token count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;conversationReference&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Conversation reference for multi-turn conversations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inferenceTimeMs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;Inference time in milliseconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isSuccessful&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Interaction success status&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;metadata&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;JSON-encoded metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;createdAt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Timestamp (ISO 8601 format)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;best-practices&quot;&gt;Best practices&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Regular cleanup&lt;/strong&gt;: Implement a periodic cleanup process to archive or delete old audit logs if required by your retention policies&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cost tracking&lt;/strong&gt;: Use token counts to monitor and optimize AI usage costs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance monitoring&lt;/strong&gt;: Track inference times to identify slow interactions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error analysis&lt;/strong&gt;: Review failed interactions to improve prompts and error handling&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metadata inspection&lt;/strong&gt;: Store and analyze metadata to understand tool invocations and structured responses&lt;/li&gt;
&lt;/ol&gt;
</description>
            <pubDate>Wed, 25 Mar 2026 08:44:45 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/ai/ai-foundation/ai-foundation-audit-logs.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/ai/ai-foundation/ai-foundation-audit-logs.html</guid>
            
            
        </item>
        
    </channel>
</rss>
