<?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, 28 Jul 2026 12:40:33 +0000</lastBuildDate>
        <generator>Jekyll v4.2.2</generator>
        
        
        <item>
            <title>AI Dev SDK Skills and Agents</title>
            <description>&lt;section class=&apos;info-block info-block--warning&apos;&gt;&lt;i class=&apos;info-block__icon icon-warning&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;&lt;div class=&quot;info-block__title&quot;&gt;Experimental module&lt;/div&gt;
&lt;p&gt;The AiDev module is experimental and not stable. There is no backward compatibility promise for this module. We welcome your feedback and contributions as we continue to develop and improve this module.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;
&lt;p&gt;The AI Dev SDK ships a set of skills and agents that codify common Spryker workflows. They reach your project in two ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Via the &lt;code&gt;ai-dev:setup&lt;/code&gt; console command&lt;/strong&gt; — copies the skills and agents into your project’s AI-tool directories (&lt;code&gt;.claude/skills/&lt;/code&gt; + &lt;code&gt;.claude/agents/&lt;/code&gt;, &lt;code&gt;.windsurf/skills/&lt;/code&gt; + &lt;code&gt;.windsurf/agents/&lt;/code&gt;, and so on). Works for every AI tool listed on the &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-overview.html#setup-command&quot;&gt;AI Dev SDK Overview&lt;/a&gt; that supports an agents directory. Codex CLI is the one exception — it has no agents directory, so agents are skipped for that tool.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Via the Claude Code plugin&lt;/strong&gt; — for Claude Code users, the &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-claude-code-plugin.html&quot;&gt;Claude Code Plugin&lt;/a&gt; installs the same skills and agents through the official marketplace, no console command required.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both delivery paths read from the same source files — the plugin just packages them for marketplace installation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What’s the difference between a skill and an agent?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Skills&lt;/strong&gt; load into the active chat on demand. Use them when you want the assistant to follow a specific workflow (write a product requirement document, refresh caches, run QA).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agents&lt;/strong&gt; are isolated sub-conversations the assistant delegates to. Use them when you want focused, single-purpose work done in its own context window (verify a behavior, diagnose a failure).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You do not need to remember the names — the assistant picks the right skill or agent from your prompt. The tables below explain what each one does so you know what to expect.&lt;/p&gt;
&lt;h2 id=&quot;how-to-invoke&quot;&gt;How to invoke&lt;/h2&gt;
&lt;p&gt;In most cases you do not need to call a skill or agent by name. Describe what you want — &lt;em&gt;“build this feature”&lt;/em&gt;, &lt;em&gt;“verify the new fee shows on the cart”&lt;/em&gt;, &lt;em&gt;“write a product requirement document for…”&lt;/em&gt; — and the assistant matches your wording to the right skill or agent from the set installed in your project.&lt;/p&gt;
&lt;p&gt;If you want to invoke one explicitly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Claude Code plugin&lt;/strong&gt; — every skill is also available as a slash command. Type &lt;code&gt;/&amp;lt;skill-name&amp;gt;&lt;/code&gt; (for example &lt;code&gt;/spryker-customization&lt;/code&gt;) and the skill loads into the current chat. The full list of commands is on the &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-claude-code-plugin.html#skills&quot;&gt;Claude Code Plugin&lt;/a&gt; page.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Other AI tools&lt;/strong&gt; — invocation follows the tool’s own convention for skills and agents. In Cursor, Windsurf, Copilot, OpenCode, and Codex CLI, the skills and agents land in the tool-specific directories (&lt;code&gt;.cursor/&lt;/code&gt;, &lt;code&gt;.windsurf/&lt;/code&gt;, etc.) and are picked up automatically by your assistant. The &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-overview.html#setup-command&quot;&gt;AI Dev SDK Overview&lt;/a&gt; lists the output paths per tool.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agents (subagents)&lt;/strong&gt; are not invoked by the user directly — they are spawned by the assistant or by a parent skill when the work calls for an isolated context. You can suggest one by name in your prompt (&lt;em&gt;“use the spryker-verifier to…”&lt;/em&gt;), but the assistant decides when to spawn.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;skills&quot;&gt;Skills&lt;/h2&gt;
&lt;p&gt;Skills are delivered through &lt;code&gt;ai-dev:setup&lt;/code&gt; (all supported AI tools) or the Claude Code plugin.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Benefits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ai-dev-setup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Generate rules, an agents/context file, and skills for the project and the chosen AI tool&lt;/td&gt;
&lt;td&gt;One command sets up consistent AI tooling for the whole team&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;code-review&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Review staged or PR changes against Spryker coding standards&lt;/td&gt;
&lt;td&gt;Catches Spryker-specific issues before they reach a pull request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;propel-schema&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create and modify Propel ORM schema files&lt;/td&gt;
&lt;td&gt;Follows Spryker schema conventions automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;data-import&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create and modify data import CSV files and importers&lt;/td&gt;
&lt;td&gt;Generates importers that fit Spryker’s data-import path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;codecept-functional&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Generate Codeception functional tests&lt;/td&gt;
&lt;td&gt;Tests follow Spryker test patterns out of the box&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;static-validation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run and interpret static analysis tools (PHPStan, PHP CS Fixer)&lt;/td&gt;
&lt;td&gt;Quick diagnosis of style and type issues without context-switching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;payment-template&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Scaffold payment method integration&lt;/td&gt;
&lt;td&gt;Follows Spryker payment module patterns end-to-end&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;yves-atomic-frontend&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create atomic design components for the Yves storefront&lt;/td&gt;
&lt;td&gt;Components match the project’s atomic conventions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;product-requirement-document&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Turn a feature idea into a research-grounded product requirement document before any code is written&lt;/td&gt;
&lt;td&gt;Spec-before-code; assigns a real Spryker actor to every story; cuts ambiguity before implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-customization-workflow.html&quot;&gt;&lt;code&gt;spryker-customization&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Walk a product requirement document or set of acceptance criteria through to a committed branch&lt;/td&gt;
&lt;td&gt;One workflow drives the full build; quality bar (PoC or MVP) chosen up-front; delegates focused work to the agents below; never auto-commits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-bugfix&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Drive a bug from an optional tracker ticket or a plain description through to a committed, validated, QA-accepted fix&lt;/td&gt;
&lt;td&gt;Orchestrates reproduce, root-cause, minimal fix, functional test, static validation, review, QA, and final verification; a shared attempt budget loops back on any failed gate; Autonomous mode adds a pushed Draft PR with a remote-CI watch loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-refresher&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run the right post-change console and composer commands after edits&lt;/td&gt;
&lt;td&gt;Owns the file-to-command mapping (codegen, caches, frontend builds, class-resolver); no missed cache rebuilds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-qa-coverage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Turn acceptance criteria into a four-bucket test plan executed against the live app&lt;/td&gt;
&lt;td&gt;Coverage goes beyond literal ACs — happy / negative / authorization / corner cases; reports pass/fail with real evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-docs-research&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Look up the right answer in official Spryker documentation&lt;/td&gt;
&lt;td&gt;Grounds AI work in documented behavior rather than the model’s memory; falls back gracefully when MCP tools are unavailable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-runtime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Drive the running Spryker application — Yves, Back Office, Merchant Portal, console, HTTP&lt;/td&gt;
&lt;td&gt;Real authenticated sessions; read-only DB / Redis / queue inspection; reusable building block for higher-level skills and agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ai-runtime-debugging&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Inspect Spryker runtime state safely from an AI session&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[AI-DEBUG]&lt;/code&gt; tagged-log pattern plus optional XDebug step-debug; built-in cleanup of debug instrumentation before commit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;project-ci-generator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transform an inherited product-style CI setup into a single, lean project CI pipeline&lt;/td&gt;
&lt;td&gt;Reads the CI that actually exists rather than applying a template; reuses the discovered commands verbatim so the result stays environment-correct; proposes a keep/drop plan for approval before deleting anything; ports the same jobs to GitLab or Bitbucket&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;agents&quot;&gt;Agents&lt;/h2&gt;
&lt;p&gt;Agents are delivered through &lt;code&gt;ai-dev:setup&lt;/code&gt; (every supported AI tool with an agents directory) or the Claude Code plugin.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Benefits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-code-reviewer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Review code against Spryker’s coding standards and architectural rules&lt;/td&gt;
&lt;td&gt;Deeper, Spryker-aware review than a generic linter; catches layer-architecture and module-convention issues&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-feature-expert&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Answer “how does feature X work in this project / in Spryker”&lt;/td&gt;
&lt;td&gt;Pushes back when the user’s framing reinvents an existing primitive; surfaces canonical patterns first; never edits code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-verifier&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Verify that a specific behavior holds in the running Spryker environment&lt;/td&gt;
&lt;td&gt;Returns PASS / FAIL / BLOCKED per acceptance criterion with raw evidence; never lies green; never tries to fix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-issue-diagnoser&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Investigate why something failed and return a root cause&lt;/td&gt;
&lt;td&gt;Reads logs, DB state, queue, search, browser console — returns a suggested direction; never attempts the fix itself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-data-seeder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create small additive test data through Spryker’s existing import path&lt;/td&gt;
&lt;td&gt;Safe and incremental — CSV + &lt;code&gt;data:import&lt;/code&gt; only; never edits code, never writes directly to the database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-screenshot-collector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Capture screenshots and GIFs of pages and flows for demos and documentation&lt;/td&gt;
&lt;td&gt;Pure capture — never asserts whether something works, never investigates failures, never edits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&quot;related&quot;&gt;Related&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-overview.html&quot;&gt;AI Dev SDK Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-mcp-server.html&quot;&gt;AI Dev MCP Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-claude-code-plugin.html&quot;&gt;Claude Code Plugin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
            <pubDate>Tue, 28 Jul 2026 12:39:52 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/ai/ai-dev/ai-dev-skills-and-agents.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/ai/ai-dev/ai-dev-skills-and-agents.html</guid>
            
            
        </item>
        
        <item>
            <title>Claude Code Plugin</title>
            <description>&lt;section class=&apos;info-block info-block--warning&apos;&gt;&lt;i class=&apos;info-block__icon icon-warning&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;&lt;div class=&quot;info-block__title&quot;&gt;Experimental module&lt;/div&gt;
&lt;p&gt;The AiDev module is experimental and not stable. There is no backward compatibility promise for this module. We welcome your feedback and contributions as we continue to develop and improve this module.&lt;/p&gt;
&lt;/div&gt;&lt;/section&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;Project must be running&lt;/div&gt;
&lt;p&gt;The MCP server runs inside your Spryker Docker container. Start your project with &lt;code&gt;docker/sdk run&lt;/code&gt; before using any skills that rely on MCP tools.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;/h2&gt;
&lt;p&gt;Working with Spryker in an AI coding assistant without project-specific context leads to a predictable pattern: the AI generates plausible-looking code that does not follow Spryker’s layer architecture, uses the wrong patterns, or misses module conventions entirely. You end up spending more time correcting mistakes than you saved.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;spryker-ai-dev-sdk&lt;/code&gt; Claude Code plugin closes this gap. It gives Claude Code deep knowledge of how Spryker projects are structured — layers, namespaces, plugin stacks, transfer objects, OMS flows — so the code it generates fits your project from the start instead of requiring repeated corrections.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What it does for you:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No more explaining Spryker basics.&lt;/strong&gt; Rules covering 20 Spryker architectural patterns are automatically loaded into every session. Claude Code knows about factories, dependency providers, expanders, mappers, and more without you having to explain them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Correct code on the first attempt.&lt;/strong&gt; Skills for common Spryker tasks — Propel schema changes, data importers, functional tests, payment integrations, atomic frontend components — follow the exact conventions your project expects.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Live project context.&lt;/strong&gt; The MCP server runs inside your Docker container and gives Claude Code real-time access to your transfer objects, module interfaces, and OMS configuration. The AI works with your actual project data, not guesses.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistent code reviews.&lt;/strong&gt; The &lt;code&gt;spryker-code-reviewer&lt;/code&gt; subagent checks your changes against Spryker’s coding standards and architectural rules, catching issues before they reach a PR.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Team-wide consistency.&lt;/strong&gt; Generated rules and context files are committed to your repository, so every developer on the team works with the same AI configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The plugin is distributed through the &lt;code&gt;spryker-plugins-official&lt;/code&gt; marketplace and installed directly inside Claude Code.&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://claude.ai/code&quot;&gt;Claude Code&lt;/a&gt; installed&lt;/li&gt;
&lt;li&gt;Your Spryker project is running: &lt;code&gt;docker/sdk run&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;install-the-plugin-from-the-marketplace&quot;&gt;Install the plugin from the marketplace&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Open Claude Code in your terminal.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the Spryker plugin marketplace:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-dev/claude-plugin-step-1.png&quot; alt=&quot;step 1&quot; /&gt;
&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-dev/claude-plugin-step-2.png&quot; alt=&quot;step 2&quot; /&gt;
&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-dev/claude-plugin-step-3.png&quot; alt=&quot;step 3&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install the plugin:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-dev/claude-plugin-step-4.png&quot; alt=&quot;step 4&quot; /&gt;
&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-dev/claude-plugin-step-5.png&quot; alt=&quot;step 5&quot; /&gt;
&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-dev/claude-plugin-step-6.png&quot; alt=&quot;step 6&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After installation, reload plugins in Claude Code.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-dev/claude-plugin-step-7.png&quot; alt=&quot;Choose output mode step 7&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;set-up-ai-tooling-for-your-project&quot;&gt;Set up AI tooling for your project&lt;/h2&gt;
&lt;p&gt;After installing the plugin, run the &lt;code&gt;ai-dev-setup&lt;/code&gt; skill to configure your project. The skill generates rules, context files, and reusable AI skills tailored to your project and AI tool.&lt;/p&gt;
&lt;p&gt;In Claude Code, run the setup skill:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-dev/claude-skill-setup.png&quot; alt=&quot;ai-dev-setup&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The skill:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Installs the &lt;code&gt;spryker-sdk/ai-dev&lt;/code&gt; package in your Spryker project.&lt;/li&gt;
&lt;li&gt;Wires up console commands for &lt;code&gt;McpServerConsole&lt;/code&gt; and &lt;code&gt;AiToolSetupConsole&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Registers the AI Dev MCP server with Claude Code.&lt;/li&gt;
&lt;li&gt;Adds &lt;code&gt;.claude/rules/&lt;/code&gt; with Spryker coding conventions and architectural guidelines.&lt;/li&gt;
&lt;li&gt;Adds &lt;code&gt;CLAUDE.md&lt;/code&gt; with project-specific context loaded into every Claude Code session.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Check with running &lt;code&gt;/context&lt;/code&gt; in Claude Code to see:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-dev/claude-install-1.png&quot; alt=&quot;claude-setup-1&quot; /&gt;
&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-dev/claude-install-2.png&quot; alt=&quot;claude-setup-2&quot; /&gt;
&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-dev/claude-install-3.png&quot; alt=&quot;claude-setup-3&quot; /&gt;
&lt;img src=&quot;https://spryker.s3.eu-central-1.amazonaws.com/docs/dg/dev/ai-dev/claude-install-4.png&quot; alt=&quot;claude-setup-4&quot; /&gt;&lt;/p&gt;
&lt;h2 id=&quot;capabilities&quot;&gt;Capabilities&lt;/h2&gt;
&lt;p&gt;For a one-page reference of every skill and agent — what each does, when to use it, and the value it adds — see &lt;a href=&quot;/docs/dg/dev/ai/ai-dev/ai-dev-skills-and-agents.html&quot;&gt;AI Dev SDK Skills and Agents&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;skills&quot;&gt;Skills&lt;/h3&gt;
&lt;p&gt;The plugin bundles the following Spryker-aware skills. Invoke them in Claude Code with the &lt;code&gt;/&lt;/code&gt; prefix.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AI Dev Setup&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:ai-dev-setup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Generates rules, a context file, and skills for your project and AI tool&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code Review&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:code-review&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reviews staged or PR changes against Spryker coding standards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Propel Schema&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:propel-schema&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Helps create and modify Propel ORM schema files following Spryker conventions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data Import&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:data-import&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Assists with creating and modifying data import CSV files and importers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codecept Functional&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:codecept-functional&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Generates Codeception functional tests following Spryker test patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static Validation&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:static-validation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runs and interprets static analysis tools (PHPStan, PHP CS Fixer)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payment Template&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:payment-template&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Scaffolds payment method integration following Spryker payment module patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Yves Atomic Frontend&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:yves-atomic-frontend&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Helps create atomic design components for the Yves frontend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Product Requirement Document&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:product-requirement-document&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Drafts a research-grounded product requirement document for a Spryker feature before implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spryker Customization&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:spryker-customization&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Orchestrates the end-to-end build of a customization from product requirement document to committed branch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spryker Bugfix&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:spryker-bugfix&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Orchestrates the end-to-end bug fix from a ticket or description to a committed, validated, QA-accepted branch (Autonomous mode adds a pushed Draft PR with a CI watch loop)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spryker Refresher&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:spryker-refresher&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runs the right post-change console and composer commands after edits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spryker QA Coverage&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:spryker-qa-coverage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Turns acceptance criteria into a four-bucket test plan and executes it against the running app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spryker Docs Research&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:spryker-docs-research&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Looks up grounded answers in the official Spryker documentation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spryker Runtime&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:spryker-runtime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Drives the running Spryker application — storefront, back office, console, HTTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI Runtime Debugging&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:ai-runtime-debugging&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Adds tagged debug logs (and optional XDebug) for inspecting Spryker runtime state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Project CI Generator&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/spryker-ai-dev-sdk:project-ci-generator&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rebuilds an inherited product-style CI setup into a single, lean project CI pipeline, keeping only the jobs and support files the project needs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;subagents&quot;&gt;Subagents&lt;/h3&gt;
&lt;p&gt;The plugin includes the following subagents. They are isolated sub-conversations that the assistant delegates to for focused, single-purpose work:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Subagent&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;spryker-code-reviewer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Performs deep code reviews of your changes against Spryker architectural patterns, coding standards, and best practices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-feature-expert&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Answers questions about how a Spryker feature, module, or capability works, grounded in docs and the project’s actual code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-verifier&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Verifies a specific behavior in the running Spryker environment and returns PASS, FAIL, or BLOCKED per acceptance criterion with raw evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-issue-diagnoser&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Investigates a failure across logs, database, queue, search, and browser state and returns a root cause&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-data-seeder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creates small additive test data through Spryker’s existing data import path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spryker-screenshot-collector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Captures screenshots and short GIFs of pages and flows for demos and documentation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;rules&quot;&gt;Rules&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;ai-dev-setup&lt;/code&gt; skill writes a set of &lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/tree/master/data/rules&quot;&gt;Spryker-specific coding rules&lt;/a&gt; into your project. These rules guide the AI to follow Spryker conventions automatically, without requiring you to explain them in every prompt.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule file&lt;/th&gt;
&lt;th&gt;What it enforces&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;business-models.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Business model structure and responsibilities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;client-zed-communication.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Client–Zed gateway communication patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;controller.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Controller conventions and responsibilities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dependency-provider.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Dependency provider wiring and plugin stacks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enforce-constants-for-control-flow.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use of constants instead of magic strings in control flow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;expander-pattern.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Expander pattern for extending transfer objects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;factory-pattern.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Factory class structure and dependency injection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;form-data-loading-performance.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Performant data loading in Zed forms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;layer-communication.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cross-layer call rules (Presentation → Communication → Business → Persistence)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;mapper-pattern.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mapper pattern for transfer-to-transfer and entity-to-transfer mappings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;module-config.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Module configuration class conventions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;naming-conventions.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Class, method, and variable naming standards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;owasp.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OWASP security guidelines applied to Spryker code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;performance.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Performance best practices (query optimization, caching)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;persistence.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Persistence layer conventions (repositories, entity managers)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;php-code-style.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PHP code style rules (PSR compliance, formatting)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;plugins.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Plugin and plugin interface implementation patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;table.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Back Office table and query container conventions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;transfer-object.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transfer object usage and immutability rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;upgradability.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Backward compatibility and upgradability guidelines&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;context-file&quot;&gt;Context file&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;ai-dev-setup&lt;/code&gt; skill generates a &lt;code&gt;CLAUDE.md&lt;/code&gt; context file based on the &lt;a href=&quot;https://github.com/spryker-sdk/ai-dev/blob/master/data/agents/AGENTS.example.md&quot;&gt;AGENTS.example.md&lt;/a&gt; template. This file is automatically loaded into every Claude Code session and provides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Common Docker CLI commands for your Spryker project&lt;/li&gt;
&lt;li&gt;Spryker application layer overview (Zed, Yves, Glue, Client, Service, Shared)&lt;/li&gt;
&lt;li&gt;Namespace and directory structure&lt;/li&gt;
&lt;li&gt;Component rules for controllers, plugins, factories, repositories, and more&lt;/li&gt;
&lt;li&gt;Abstract class references for all layers&lt;/li&gt;
&lt;/ul&gt;
&lt;section class=&apos;info-block &apos;&gt;&lt;i class=&apos;info-block__icon icon-info&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;&lt;div class=&quot;info-block__title&quot;&gt;Starting point, not a complete setup&lt;/div&gt;
&lt;p&gt;The files generated by &lt;code&gt;ai-dev-setup&lt;/code&gt; are a baseline derived from Spryker defaults. They cover general Spryker conventions but do not include anything specific to your project — custom modules, third-party integrations, team conventions, or environment details. Treat the generated &lt;code&gt;CLAUDE.md&lt;/code&gt; and rules as a starting point and extend them with your project-specific requirements.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
</description>
            <pubDate>Tue, 28 Jul 2026 12:39:52 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/ai/ai-dev/ai-dev-claude-code-plugin.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/ai/ai-dev/ai-dev-claude-code-plugin.html</guid>
            
            
        </item>
        
        <item>
            <title>Security release notes 202306.0</title>
            <description>The following information pertains to security-related issues that have been recently resolved. All issues are listed by description and affected modules.

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

## Stored XSS in product pages

Administrators can place a malicious payload into the description section of new and existing product pages in the Back Office application. This payload is then executed to all users of the shop that visit the affected product pages, resulting in a Cross-Site Scripting (XSS) vulnerability.

### Affected modules

`spryker/product-management`: 0.19.3-0.19.35

### Introduced changes

Sanitization controls have been implemented to prevent the input of malicious payloads within product forms.

### How to get the fix

To implement a fix for this vulnerability, the ProductManagement module should be updated to version 0.19.36.

If your version of `spryker/product-management` is earlier than 0.19.35, follow these steps:

1. Upgrade the `spryker/store` module to version 1.19.0 and `spryker/zed-request-extension` to version 1.1.0:

```bash
composer require spryker/store:&quot;~1.19.0&quot; spryker/zed-request-extension:&quot;~1.1.0&quot;
composer show spryker/store # Verify the version
composer show spryker/zed-request-extension # Verify the version
```

2. Upgrade the `spryker/product-management` module to version 0.19.36:

```bash
composer require spryker/product-management:&quot;~0.19.36&quot; spryker/gui:&quot;~3.48.0&quot; spryker/util-sanitize-xss:&quot;~1.1.0&quot;
composer show spryker/product-management # Verify the version
```

## Cross-company role manipulation

Because of missing access validation controls on the backend, an administrator user of a company was able to create and update roles for other companies. This was possible because of the possibility to manipulate the company ID parameter included in the HTTP requests of the role creation functionality.

### Affected modules

`spryker-shop/company-page`: 1.0.0-2.21.0

### Introduced changes

Access validation controls have been implemented to prevent administrators from being able to create and edit roles for other companies.

### How to get the fix

The update requires PHP 8 to be installed. If you are using PHP 7, see [PHP 8.1 as the minimum version for all Spryker projects](https://docs.spryker.com/docs/scos/user/intro-to-spryker/whats-new/php8-as-a-minimum-version-for-all-spryker-projects.html) for details on how to migrate to PHP 8.1.

To implement a fix for this vulnerability, upgrade the `company-page` module to version 2.22.0:

```bash
composer require spryker-shop/company-page:&quot;~2.22.0&quot;
composer show spryker-shop/company-page # Verify the version
```

## Unvalidated URL redirection

Attackers were able to bypass the redirect URL validation for URLs provided through the GET request parameter and control the website that the user is redirected to. This type of vulnerability can be used in conjunction with social engineering to coerce a victim into navigating to a malicious resource or location.

### Affected modules

`spryker/kernel`: 1.0.0-3.72.0

### Introduced changes

Additional validation controls have been implemented to prevent an attacker from being able to manipulate the location of the URL redirection.

### How to get the fix

To implement a fix for this vulnerability, update the `kernel` module:

- If your version of `spryker/kernel` is 3.72.0, update to version 3.72.1:

```bash
composer require spryker/kernel:&quot;~3.72.1&quot;
composer show spryker/kernel # Verify the version
```

- If your version of `spryker/kernel` is 3.71.0 or 3.71.1, update to version 3.71.2:

```bash
composer require spryker/kernel:&quot;~3.71.2&quot;
composer show spryker/kernel # Verify the version
```

- If your version of `spryker/kernel` is 3.70.0, update to version 3.70.1:

```bash
composer require spryker/kernel:&quot;~3.70.1&quot;
composer show spryker/kernel # Verify the version
```

- If your version of `spryker/kernel` is earlier than 3.69.0, update to version 3.68.1:

```bash
composer require spryker/kernel:&quot;~3.68.1&quot;
composer show spryker/kernel # Verify the version
```

## Brute-force attacks on the Storefront and in the Back Office

The Storefront, the Back Office, and the Merchant portals were prone to brute-force attacks. By exploiting this type of vulnerability, an attacker was able to systematically attempt different combinations of usernames and passwords against the login pages of the affected portals until a valid combination was identified.

### Affected modules

`spryker/security-blocker`: 1.0.0-1.1.1

### Introduced changes

Maximum login attempts and blocking time can be configured for the affected portals.

### How to get the fix

1. Upgrade the `spryker/security-blocker` module version to 1.2.0:

```bash
composer require spryker/security-blocker:&quot;~1.2.0&quot;
composer show spryker/security-blocker # Verify the version
```

2. Upgrade the `spryker/error-handler` module version to 2.8.0:

```bash
composer require spryker/error-handler:&quot;~2.8.0&quot;
composer show spryker/error-handler # Verify the version
```

3. Install the `spryker/security-blocker-backoffice` module version 1.0.0:

```bash
composer require spryker/security-blocker-backoffice:&quot;~1.0.0&quot;
composer show spryker/security-blocker-backoffice # Verify the version
```

4. Install the `spryker/security-blocker-backoffice-gui` module version 1.0.0:

```bash
composer require spryker/security-blocker-backoffice-gui:&quot;~1.0.0&quot;
composer show spryker/security-blocker-backoffice-gui # Verify the version
```

5. Install the `spryker/security-blocker-storefront-customer` module version 1.0.0:

```bash
composer require spryker/security-blocker-storefront-customer:&quot;~1.0.0&quot;
composer show spryker/security-blocker-storefront-customer # Verify the version
```

6. Install the `spryker/security-blocker-storefront-agent` module version 1.0.0:

```bash
composer require spryker/security-blocker-storefront-agent:&quot;~1.0.0&quot;
composer show spryker/security-blocker-storefront-agent # Verify the version
```

7. Generate transfers:

```bash
console transfer:generate
```

8. Add configuration to `config/Shared/config_default.php`:

```php
// &gt;&gt;&gt; Security Blocker Storefront Agent
$config[SecurityBlockerStorefrontAgentConstants::AGENT_BLOCK_FOR_SECONDS] = 360;
$config[SecurityBlockerStorefrontAgentConstants::AGENT_BLOCKING_TTL] = 900;
$config[SecurityBlockerStorefrontAgentConstants::AGENT_BLOCKING_NUMBER_OF_ATTEMPTS] = 9;

// &gt;&gt;&gt; Security Blocker Storefront Customer
$config[SecurityBlockerStorefrontCustomerConstants::CUSTOMER_BLOCK_FOR_SECONDS] = 360;
$config[SecurityBlockerStorefrontCustomerConstants::CUSTOMER_BLOCKING_TTL] = 900;
$config[SecurityBlockerStorefrontCustomerConstants::CUSTOMER_BLOCKING_NUMBER_OF_ATTEMPTS] = 9;

// &gt;&gt;&gt; Security Blocker BackOffice user
$config[SecurityBlockerBackofficeConstants::BACKOFFICE_USER_BLOCKING_TTL] = 900;
$config[SecurityBlockerBackofficeConstants::BACKOFFICE_USER_BLOCK_FOR_SECONDS] = 360;
$config[SecurityBlockerBackofficeConstants::BACKOFFICE_USER_BLOCKING_NUMBER_OF_ATTEMPTS] = 9;
```

9. Add translations to `data/import/common/common/glossary.csv`:

```csv
security_blocker_backoffice_gui.error.account_blocked,&quot;Too many log-in attempts from your address. Please wait %minutes% minutes before trying again.&quot;,en_US
security_blocker_backoffice_gui.error.account_blocked,&quot;Warten Sie bitte %minutes% Minuten, bevor Sie es erneut versuchen.&quot;,de_DE
```

10. Import glossary:

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

11. Register plugins in `SecurityBlockerDependencyProvider`:

```php
&lt;?php

namespace Pyz\Client\SecurityBlocker;

use Spryker\Client\SecurityBlocker\SecurityBlockerDependencyProvider as SprykerSecurityBlockerDependencyProvider;
use Spryker\Client\SecurityBlockerBackoffice\Plugin\SecurityBlocker\BackofficeUserSecurityBlockerConfigurationSettingsExpanderPlugin;
use Spryker\Client\SecurityBlockerStorefrontAgent\Plugin\SecurityBlocker\AgentSecurityBlockerConfigurationSettingsExpanderPlugin;
use Spryker\Client\SecurityBlockerStorefrontCustomer\Plugin\SecurityBlocker\CustomerSecurityBlockerConfigurationSettingsExpanderPlugin;

class SecurityBlockerDependencyProvider extends SprykerSecurityBlockerDependencyProvider
{
    /**
     * @return list&lt;\Spryker\Client\SecurityBlockerExtension\Dependency\Plugin\SecurityBlockerConfigurationSettingsExpanderPluginInterface&gt;
     */
    protected function getSecurityBlockerConfigurationSettingsExpanderPlugins(): array
    {
        return [
            new BackofficeUserSecurityBlockerConfigurationSettingsExpanderPlugin(),
            new AgentSecurityBlockerConfigurationSettingsExpanderPlugin(),
            new CustomerSecurityBlockerConfigurationSettingsExpanderPlugin(),
        ];
    }
}
```

12. Adjust `src/Pyz/Zed/ErrorHandler/ErrorHandlerConfig.php`:

```php
&lt;?php

namespace Pyz\Zed\ErrorHandler;

use Spryker\Zed\ErrorHandler\ErrorHandlerConfig as SprykerErrorHandlerConfigAlias;
use Symfony\Component\HttpFoundation\Response;

/**
 * @method \Spryker\Shared\ErrorHandler\ErrorHandlerConfig getSharedConfig()
 */
class ErrorHandlerConfig extends SprykerErrorHandlerConfigAlias
{
    /**
     * @api
     *
     * @return array&lt;int&gt;
     */
    public function getValidSubRequestExceptionStatusCodes(): array
    {
        return array_merge(
            parent::getValidSubRequestExceptionStatusCodes(),
            [
                Response::HTTP_TOO_MANY_REQUESTS,
            ],
        );
    }
}
```

13. Register plugins in `src/Pyz/Yves/EventDispatcher/EventDispatcherDependencyProvider.php`:

```php
class EventDispatcherDependencyProvider extends SprykerEventDispatcherDependencyProvider
{
...
    protected function getEventDispatcherPlugins(): array
    {
        return [
            ...
            new SecurityBlockerBackofficeUserEventDispatcherPlugin(),
        ];
    }
...
```

If Merchant Portal is also installed, follow these steps:

1. Install the `spryker/security-blocker-merchant-portal` module version 1.0.0:

```bash
composer require spryker/security-blocker-merchant-portal:&quot;~1.0.0&quot;
composer show spryker/security-blocker-merchant-portal # Verify the version
```

2. Install the `spryker/security-blocker-merchant-portal-gui` module version 1.0.0:

```bash
composer require spryker/security-blocker-merchant-portal-gui:&quot;~1.0.0&quot;
composer show spryker/security-blocker-merchant-portal-gui # Verify the version
```

3. Generate transfers:

```bash
console transfer:generate
```

4. Register plugins in `src/Pyz/Zed/EventDispatcher/EventDispatcherDependencyProvider.php`:

```php
class EventDispatcherDependencyProvider extends SprykerEventDispatcherDependencyProvider
{
...
    protected function getEventDispatcherPlugins(): array
    {
        return [
            ...
            new SecurityBlockerMerchantPortalUserEventDispatcherPlugin(),
        ];
    }
...
```

5. Register plugins in `SecurityBlockerDependencyProvider`:

```php
&lt;?php

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

namespace Pyz\Client\SecurityBlocker;

use Spryker\Client\SecurityBlocker\SecurityBlockerDependencyProvider as SprykerSecurityBlockerDependencyProvider;
use Spryker\Client\SecurityBlockerMerchantPortal\Plugin\SecurityBlocker\MerchantPortalUserSecurityBlockerConfigurationSettingsExpanderPlugin;

class SecurityBlockerDependencyProvider extends SprykerSecurityBlockerDependencyProvider
{
    /**
     * @return list&lt;\Spryker\Client\SecurityBlockerExtension\Dependency\Plugin\SecurityBlockerConfigurationSettingsExpanderPluginInterface&gt;
     */
    protected function getSecurityBlockerConfigurationSettingsExpanderPlugins(): array
    {
        return [
            ...
            new MerchantPortalUserSecurityBlockerConfigurationSettingsExpanderPlugin(),
        ];
    }
}
```

6. Add configuration to `config/Shared/config_default.php`:

```bash
// &gt;&gt;&gt; Security Blocker MerchantPortal user
$config[SecurityBlockerMerchantPortalConstants::MERCHANT_PORTAL_USER_BLOCK_FOR_SECONDS] = 360;
$config[SecurityBlockerMerchantPortalConstants::MERCHANT_PORTAL_USER_BLOCKING_TTL] = 900;
$config[SecurityBlockerMerchantPortalConstants::MERCHANT_PORTAL_USER_BLOCKING_NUMBER_OF_ATTEMPTS] = 9;
```

7. Add translations to `data/import/common/common/glossary.csv` :

```csv
security_blocker_merchant_portal_gui.error.account_blocked,&quot;Too many log in attempts from your address. Please wait %minutes% minutes before trying again.&quot;,en_US
security_blocker_merchant_portal_gui.error.account_blocked,&quot;Warten Sie bitte %minutes% Minuten, bevor Sie es erneut versuchen.&quot;,de_DE
```

8. Import glossary:

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

## Weak input validation for the customer address field

The parameters related to the address field had insufficient server-side input validation. By supplying invalid or potentially malicious parameter values, an attacker might be able to cause the server to respond in an unexpected way.

### Affected modules

- `spryker-shop/customer-page`: 0.1.0-2.41.0
- `spryker-shop/company-page`: 0.0.1-2.22.0
- `spryker/customer`: 0.20.0-7.51.2
- `spryker/company-unit-address-gui`: 0.1.0-1.3.0
- `spryker/merchant-profile-gui`: 0.1.0-1.2.0

### Introduced changes

Input validation controls have been implemented on the server side to validate values submitted in the address field.

### How to get the fix

To implement a fix for this vulnerability, update the `shop-ui`, `customer-page`, `company-page`, `customer`, `company-unit-address-gui`, and `merchant-profile-gui` modules:

1. Upgrade the `spryker-shop/shop-ui` module to at least version 1.70.0:

```bash
composer require spryker-shop/shop-ui:&quot;^1.70.0&quot;
composer show spryker-shop/customer-page # Verify the version
```

2. Add the `SanitizeXssTypeExtensionFormPlugin` plugin to `FormDependencyProvider`:

**src/Pyz/Yves/Form/FormDependencyProvider.php**

```bash
&lt;?php

namespace Pyz\Yves\Form;

use Spryker\Yves\Form\FormDependencyProvider as SprykerFormDependencyProvider;
use SprykerShop\Yves\ShopUi\Plugin\Form\SanitizeXssTypeExtensionFormPlugin;

class FormDependencyProvider extends SprykerFormDependencyProvider
{
    /**
     * @return list&lt;\Spryker\Shared\FormExtension\Dependency\Plugin\FormPluginInterface&gt;
     */
    protected function getFormPlugins(): array
    {
        return [
            new SanitizeXssTypeExtensionFormPlugin(),
        ];
    }
}
```

3. Upgrade the `spryker-shop/customer-page` module to version 2.42.0:

```bash
composer require spryker-shop/customer-page:&quot;~2.42.0&quot;
composer show spryker-shop/customer-page # Verify the version
```

4. Upgrade the `spryker-shop/company-page` module to version 2.23.0:

```bash
composer require spryker-shop/company-page:&quot;~2.23.0&quot;
composer show spryker-shop/company-page # Verify the version
```

5. Upgrade the `spryker/customer` module:

   1. If your version of `spryker/customer` is 7.42.0 and later, update it to version 7.51.3:

    ```bash
    composer require spryker/customer:&quot;~7.51.3&quot;
    composer show spryker/customer # Verify the version
    ```

   2. If your version of `spryker/customer` is earlier than 7.42.0, update it to version 7.42.1:

    ```bash
    composer require spryker/customer:&quot;~7.42.1&quot;
    composer show spryker/customer # Verify the version
    ```

   3. If your version of `spryker/customer` is earlier than 7.50.0, update it to version 7.50.1:

    ```bash
    composer require spryker/customer:&quot;~7.50.1&quot;
    composer show spryker/customer # Verify the version
    ```

6. Upgrade the `spryker/company-unit-address-gui` module to version 1.3.1:

```bash
composer require spryker/company-unit-address-gui:&quot;~1.3.1&quot;
composer show spryker/company-unit-address-gui # Verify the version
```

7. Upgrade the `spryker/merchant-profile-gui` module version to 1.2.1:

```bash
composer require spryker/merchant-profile-gui:&quot;~1.2.1&quot;
composer show spryker/merchant-profile-gui # Verify the version
```

## Outdated third-party library guzzlehttp/psr7

An outdated version of the `guzzlehttp/psr7` library was identified to affect Spryker&apos;s applications. The version in use, 2.4.3, was affected by a publicly known vulnerability that could let an attacker sneak in a newline (\n) into both the header names and values (CVE-2023-29197).

### Affected modules

- `spryker/guzzle`: 0.20.0-2.4.0
- `spryker/message-broker-aws`: 1.0.0-1.4.2
- `spryker/secrets-manager-aws`: 1.0.0-1.0.1
- `spryker/oauth-auth0` : 1.0.0

### Introduced changes

The affected library has been upgraded.

### How to get the fix

To implement a fix for this vulnerability, update the `guzzle`, `message-broker-aws`, `secrets-manager-aws`, and  `oauth-auth0` modules:

1. Upgrade the `spryker/guzzle` module to version 2.4.1:

```bash
composer require spryker/guzzle:&quot;^2.4.1&quot;
composer show spryker/guzzle # Verify the version
```

2. Upgrade the `spryker/message-broker-aws` module to version 1.4.3:

```bash
composer require spryker/message-broker-aws:&quot;^1.4.3&quot;
composer show spryker/message-broker-aws # Verify the version
```

3. Upgrade the `spryker/secrets-manager-aws` module to version 1.0.2:

```bash
composer require spryker/secrets-manager-aws:&quot;^1.0.2&quot;
composer show spryker/secrets-manager-aws # Verify the version
```

4. Upgrade the `spryker/oauth-auth0` module to version 1.0.1:

```bash
composer require spryker/oauth-auth0:&quot;^1.0.1&quot;
composer show spryker/oauth-auth0 # Verify the version
```

## Missing security HTTP headers

Security-related HTTP headers were missing from Spryker&apos;s applications. Adding these headers improves the overall security posture of the applications because they implement an additional layer of protection against common web application attacks.

### Affected modules

- `spryker/event-dispatcher`: &lt;=1.4.0
- `spryker/glue-backend-api-application`: &lt;=1.3.0
- `spryker/glue-storefront-api-application`: &lt;=1.2.0
- `spryker/http`: &lt;=1.10.0
- `spryker/merchant-portal-application`: &lt;=1.1.0

### Introduced changes

The following security-related HTTP headers can be implemented:
- `Strict-Transport-Security`
- `Cache-Control`
- `X-Content-Type-Options`
- `X-Frame-Options`
- `X-XSS-Protection`
- `Content-Security-Policy`

### How to get the fix

To implement a fix for this vulnerability:

1. Update the `event-dispatcher`, `glue-backend-api-application`, `glue-storefront-api-application`, `HTTP`, and `merchant-portal-application` modules:

```bash
composer update spryker/event-dispatcher spryker/glue-backend-api-application spryker/glue-storefront-api-application spryker/http spryker/merchant-portal-application
```

2. Register `Spryker\Glue\Http\Plugin\EventDispatcher\CacheControlHeaderEventDispatcherPlugin` in `Pyz\Glue\EventDispatcher::getEventDispatcherPlugins()`.

3. Register `Spryker\Glue\GlueBackendApiApplication\Plugin\GlueApplication\StrictTransportSecurityHeaderResponseFormatterPlugin` in `Pyz\Glue\GlueBackendApiApplication\GlueBackendApiApplicationDependencyProvider::getResponseFormatterPlugins()`.

4. In `Pyz\Glue\GlueStorefrontApiApplication\GlueStorefrontApiApplicationDependencyProvider::getResponseFormatterPlugins()`, register `Spryker\Glue\GlueStorefrontApiApplication\Plugin\GlueApplication\StrictTransportSecurityHeaderResponseFormatterPlugin`.

5. In `Pyz\Yves\EventDispatcher\EventDispatcherDependencyProvider::getEventDispatcherPlugins()`, register `Spryker\Yves\Http\Plugin\EventDispatcher\CacheControlHeaderEventDispatcherPlugin`.

6. In `Pyz\Yves\EventDispatcher\EventDispatcherDependencyProvider::getEventDispatcherPlugins()`, register `Spryker\Yves\Http\Plugin\EventDispatcher\EnvironmentInfoHeaderEventDispatcherPlugin`.

7. Remove deprecated `Spryker\Yves\Http\Plugin\EventDispatcher\HeaderEventDispatcherPlugin` from `Pyz\Yves\EventDispatcher\EventDispatcherDependencyProvider::getEventDispatcherPlugins()`.

8. In `Pyz\Zed\EventDispatcher\EventDispatcherDependencyProvider::getEventDispatcherPlugins()`, register `Spryker\Zed\Http\Communication\Plugin\EventDispatcher\CacheControlHeaderEventDispatcherPlugin`.

9. In `Pyz\Zed\EventDispatcher\EventDispatcherDependencyProvider::getEventDispatcherPlugins()`, register `Spryker\Zed\Http\Communication\Plugin\EventDispatcher\EnvironmentInfoHeaderEventDispatcherPlugin`.

10. In `Pyz\Zed\EventDispatcher\EventDispatcherDependencyProvider::getMerchantPortalEventDispatcherPlugins()`, register `Spryker\Zed\MerchantPortalApplication\Communication\Plugin\EventDispatcher\HeadersSecurityEventDispatcherPlugin`.

11. Remove deprecated `Spryker\Zed\Http\Communication\Plugin\EventDispatcher\HeaderEventDispatcherPlugin` from `Pyz\Zed\EventDispatcher\EventDispatcherDependencyProvider::getEventDispatcherPlugins()`.

12. In `Pyz\Zed\MerchantPortalApplication\MerchantPortalApplicationDependencyProvider::getMerchantPortalApplicationPlugins()`, register `Spryker\Zed\EventDispatcher\Communication\Plugin\MerchantPortalApplication\MerchantPortalEventDispatcherApplicationPlugin`.

13. Overwrite `Pyz\Glue\GlueBackendApiApplication\GlueBackendApiApplicationConfig::getSecurityHeaders()` and `Pyz\Glue\GlueStorefrontApiApplication\GlueStorefrontApiApplicationConfig::getSecurityHeaders()` to set Cache-Control security header:

```php
/**
 * @return array&lt;string, string&gt;
 */
public function getSecurityHeaders(): array
{
    return array_merge(
        parent::getSecurityHeaders(),
        [&apos;Cache-Control&apos; =&gt; &apos;no-cache, private&apos;],
    );
}
```

14. Optional: adjust `config/Shared/config_default.php` to add cache control configuration. To see the list of available directives, check `Spryker\Yves\Http\Plugin\EventDispatcher\CacheControlHeaderEventDispatcherPlugin`.

{% info_block warningBox &quot;Never enable public caching globally&quot; %}

These values are applied to *every* response of the application—the plugin cannot exempt individual pages. Setting `&apos;public&apos; =&gt; true` with `&apos;max-age&apos;` makes the cart, checkout, and customer account pages cacheable, so browsers serve a stale cart and a CDN can serve one customer&apos;s cart to another customer.

If you skip this step, the applications keep the Symfony default `Cache-Control: no-cache, private`, which is safe.

{% endinfo_block %}

```php
use Spryker\Shared\Http\HttpConstants;

$config[HttpConstants::YVES_HTTP_CACHE_CONTROL_CONFIG] = [
   &apos;private&apos; =&gt; true,
   &apos;no-cache&apos; =&gt; true,
];

$config[HttpConstants::ZED_HTTP_CACHE_CONTROL_CONFIG] = [
   &apos;private&apos; =&gt; true,
   &apos;no-cache&apos; =&gt; true,
];

$config[HttpConstants::GLUE_HTTP_CACHE_CONTROL_CONFIG] = [
   &apos;private&apos; =&gt; true,
   &apos;no-cache&apos; =&gt; true,
];
```
</description>
            <pubDate>Tue, 28 Jul 2026 12:32:55 +0000</pubDate>
            <link>https://docs.spryker.com/docs/about/all/releases/security-releases/security-release-notes-202306.0.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/about/all/releases/security-releases/security-release-notes-202306.0.html</guid>
            
            
        </item>
        
        <item>
            <title>Security guidelines</title>
            <description>This document describes the data security guidelines you need to follow on the application level. In cloud environments, infrastructure security measures are implemented by default, so they&apos;re not described.

## Passwords

The most important about password security is to not save passwords in plain text. Therefore, Spryker uses BCrypt based on Blowfish to hash passwords and add a random salt to each hash, preventing rainbow table attacks. To prevent dictionary and brute force attacks, you can force users to use special characters by adding validation rules to needed forms. For even higher security, use two-factor authentication and CAPTCHA.

## Secrets

Store secrets, API keys, and similar sensitive data in a dedicated secrets management system rather than in `./config/Shared/**` and `./deploy.*.yml` files.

For more information about secrets and parameters, see [Add variables in the Parameter Store](/docs/ca/dev/add-variables-in-the-parameter-store.html). We recommend establishing a regular cadence of rotating secrets. For recommendations on establishing a secrets rotation policy, see [Operational Best Practices for CIS AWS Foundations Benchmark v1.4 Level 1 1.14](https://docs.aws.amazon.com/config/latest/developerguide/operational-best-practices-for-cis_aws_benchmark_level_1.html).

## Hardcoded passwords

Avoid using demo users in production environments.

For example, installer users are used in `UserInstallerPlugin`, which runs during a destructive deployment.

```php
namespace Pyz\Zed\User;

class UserConfig extends \Spryker\Zed\User\UserConfig
{
    public function getInstallerUsers(): array
    {
        return [
            [
                &apos;firstName&apos; =&gt; &apos;Admin&apos;,
                &apos;lastName&apos; =&gt; &apos;Spryker&apos;,
                &apos;username&apos; =&gt; &apos;admin@spryker.com&apos;,
                &apos;password&apos; =&gt; &apos;change123&apos;,
                &apos;localeName&apos; =&gt; &apos;de_DE&apos;,
                &apos;isAgent&apos; =&gt; 1,
            ],
            // ...
        ];
    }
}
```

We recommend moving installer user config to Parameter Store before the release of production. For more information, see [Add variables in the Parameter Store](/docs/ca/dev/add-variables-in-the-parameter-store.html).

Also, make sure to remove demo customers from the `customer.csv` data import for production environments.

## Encrypted communication

Because HTTP is a textual protocol having no built-in encryption, passwords and customer personal data are transferred to shops in plain text. So, a good practice is to configure and implement transport layer security (TLS), which is widely known to most users as HTTPS.

In most cases, it prevents eavesdropping on traffic of users in local public networks like free Wi-Fi hotspots. Besides, it can be used to authenticate users using third-party integrations by requiring a client certificate to be trusted.

You can force HTTPS for the Storefront, Back Office, and Glue using the `Strict-Transport-Security` header:
- `HttpConstants::ZED_HTTP_STRICT_TRANSPORT_SECURITY_ENABLED`
- `HttpConstants::YVES_HTTP_STRICT_TRANSPORT_SECURITY_ENABLED`
- `HttpConstants::ZED_HTTP_STRICT_TRANSPORT_SECURITY_CONFIG`
- `HttpConstants::YVES_HTTP_STRICT_TRANSPORT_SECURITY_CONFIG`
- `HttpConstants::GLUE_HTTP_STRICT_TRANSPORT_SECURITY_ENABLED`
- `HttpConstants::GLUE_HTTP_STRICT_TRANSPORT_SECURITY_CONFIG`

## Access to the Back Office and Merchant Portal

The Back Office and Merchant Portal applications serve as administration panels. So, we highly recommend adding an extra layer of security by introducing a VPN, IP whitelisting, or additional authentication. This ensures that only authorized users have access to them.

## Allowlisting IP addresses of third-party systems

We highly recommend allowlisting the IP Addresses of third-party systems, such as ERP or WMS. To request allowlisting, provide the IP addresses or CIDR by [creating a support case](https://support.spryker.com)

## Security headers

Security headers are directives used by web applications to configure security defenses in web browsers. Based on these directives, browsers can make it harder to exploit client-side vulnerabilities such as Cross-Site Scripting or Clickjacking. Headers can also be used to configure the browser to only allow valid TLS communication and enforce valid certificates, or even enforce using a specific server certificate.

The following sections describe the configuration places for various security headers. You can change them on the project level.

### X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, Content-Security-Policy

#### Yves

Default security headers are located in: `\Spryker\Yves\Application\ApplicationConfig::getSecurityHeaders()`.

Default values:

```yml
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Content-Security-Policy: frame-ancestors &apos;self&apos;; sandbox allow-downloads allow-forms allow-modals allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-top-navigation; base-uri &apos;self&apos;; form-action &apos;self&apos;
```

#### Zed

Default security headers are located in: `\Spryker\Zed\Application\ApplicationConfig::getSecurityHeaders()`.

Default values:

```yml
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Content-Security-Policy: frame-ancestors &apos;self&apos;
```

#### Glue

Default security headers are located in:  `\Spryker\Glue\GlueApplication\GlueApplicationConfig::getSecurityHeaders()`.

Default values:

```yml
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Content-Security-Policy: frame-ancestors &apos;self&apos;
```

#### Glue Storefront

Default security headers are located in: `\Spryker\Glue\GlueStorefrontApiApplication\GlueStorefrontApiApplicationConfig:::getSecurityHeaders()`.

Default values:

```yml
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Content-Security-Policy: frame-ancestors &apos;self&apos;
```

#### Glue Backend

Default security headers are located in: `\Spryker\Glue\GlueBackendApiApplication\GlueBackendApiApplicationConfig::getSecurityHeaders()`.

Default values:

```yml
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Content-Security-Policy: frame-ancestors &apos;self&apos;
```

#### Cache-Control header

You can set a custom `Cache-Control` header for the Storefront, Back Office, and Glue using the following plugins:

- `Spryker\Zed\Http\Communication\Plugin\EventDispatcher\CacheControlHeaderEventDispatcherPlugin`:
  - Add the plugin into an application specific method for Zed using `\Pyz\Zed\EventDispatcher\EventDispatcherDependencyProvider::getEventDispatcherPlugins()`
  - Plugin configuration: `Spryker\Shared\Http\HttpConstants::ZED_HTTP_CACHE_CONTROL_CONFIG`

- `Spryker\Yves\Http\Plugin\EventDispatcher\CacheControlHeaderEventDispatcherPlugin`:
  - Add the plugin into an application specific method for Yves using `\Pyz\Yves\EventDispatcher\EventDispatcherDependencyProvider::getEventDispatcherPlugins()`
  - Plugin configuration: `Spryker\Shared\Http\HttpConstants::YVES_HTTP_CACHE_CONTROL_CONFIG`

- `Spryker\Glue\Http\Plugin\EventDispatcher\CacheControlHeaderEventDispatcherPlugin`
  - Add the plugin into an application specific method for Glue using `\Pyz\Glue\EventDispatcher\EventDispatcherDependencyProvider::getEventDispatcherPlugins()`
  - Plugin configuration: `Spryker\Shared\Http\HttpConstants::GLUE_HTTP_CACHE_CONTROL_CONFIG`

{% info_block warningBox &quot;Never enable public caching globally&quot; %}

These values are applied to *every* response of the application—the plugins cannot exempt individual pages. Setting `&apos;public&apos; =&gt; true` with `&apos;max-age&apos;` makes the cart, checkout, and customer account pages cacheable, so browsers serve a stale cart and a CDN can serve one customer&apos;s cart to another customer.

If you do not configure these keys, the applications keep the Symfony default `Cache-Control: no-cache, private`, which is safe.

{% endinfo_block %}


## Session security and hijacking

Websites include many third-party JavaScript libraries that can access the content of a page.

- To prevent access to session cookies from Javascript, the HttpOnly attribute of the session cookie is set by default. We recommend setting this attribute for all sensitive cookies.
- When using TLS, you can use a `secure` cookie flag to instruct a browser to send this cookie back to the server only via an encrypted connection. To configure it, use the `*_SESSION_COOKIE_SECURE` configuration keys.
- To prevent session fixation, session identifier is refreshed on login and logout events. We recommend implementing the same behavior for other sensitive cookies if you use them in your shop.
- Make sure that your web server configuration does not cut these flags from cookie headers.
- Make sure that `*_SESSION_COOKIE_DOMAIN` matches only your domain to disallow a browser to send the cookie to another domain or subdomain.
- Never send a session ID as a GET parameter of a URL, because the ID can be logged in logs or forwarded to external websites in HTTP Referer header.

## Cross-site request forgery (CSRF)

CSRF forces a user to execute unwanted actions while being logged in and either click on a specially crafted link or just embed the URL into some HTML tags triggering the request automatically—for example, the `src` attribute of `img`. To prevent such attacks, Symfony Form provides the `csrf_protection` token by default. We recommend using it in all forms.

## Cross-site scripting (XSS)

Cross-site scripting is a possibility to inject malicious scripts to be executed in the browser context, for example, for a logged-in user to scrape information from the page or steal cookies. To prevent such vulnerabilities, developers should filter input and sanitize output to prevent rendering HTML or JS code from user input.

Twig template engine has autoescaping enabled by default, so make sure to not use a `raw` filter. HTML should be stripped from user input.

Usually, shop operators are trusted to enter raw HTML. Because you can&apos;t limit them in this case, we recommend restricting access to the Back Office and other administrative panels in your shop. For example, introduce a VPN, IP whitelisting, or additional authentication.

Additionally, you can set [X-XSS-Protection](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection) using `HeadersSecurityEventDispatcherPlugin`.

## Security between the Storefront and the Back Office

The Storefront uses HTTP RPC calls to communicate with the Back Office. Secure these calls by enabling Back Office authorization for Storefront requests:

- Set `ZedRequestConstants::AUTH_ZED_ENABLED` and `AuthConstants::AUTH_ZED_ENABLED` to true.
- Set a random value of `AuthConstants::AUTH_DEFAULT_CREDENTIALS[‘zed_request’][‘token’]` for each environment.
- Optional: Change the username used by Storefront in `UserConstants::USER_SYSTEM_USERS`.

## Remote code execution

Avoid triggering remote code execution as follows:

- Local file inclusion: using unsanitized paths in `include` statements. To limit locations of files to be included, use the `include_path` PHP configuration option.
- Remote file inclusion: avoid using unsanitized URLs or user input in `include` statements.
- Unsafe deserialization. Serialized data should not be sent to the browser and should not be accepted back by the server. During deserialization of serialized data, PHP might instantiate classes mentioned in the payload and invoke some actions. Avoid this behavior or implement signature verification methods to validate this input.
- Command injection: avoid forwarding user input to `exec`, `system`, `passthru`, or similar functions.

## SQL injection

SQL injections are happening when unsanitized user input is embedded into an SQL statement. Use the following mechanisms to prevent SQL injections:

- Propel to build queries and avoid plain SQL.
- Prepared statements (used by Propel by default) and typed placeholders.
- Casting incoming data to concrete data types like integer or string.
- The `CastId` method in Zed controllers.

## Clickjacking

Clickjacking is when UI tweaked to force users to click on specific buttons or links. To prevent clickjacking, set correct headers in `X-Frame-Options` and `Content-Security-Policy` provided by `HeadersSecurityEventDispatcherPlugin`. Make sure that the headers are not deleted by webserver configuration. For more information about clickjacking, see the [OWASP](https://owasp.org/www-community/attacks/Clickjacking) article.

## Obsolete or outdated dependencies

To make sure that all the security updates are installed, keep Spryker and third-party modules up to date. For upgradability guidelines, see [Keeping a project upgradable](/docs/dg/dev/guidelines/keeping-a-project-upgradable/keeping-a-project-upgradable.html).

## Packages security vulnerabilities

To be up-to-speed with the security vulnerabilities, we recommend doing the following:

- Check if Spryker packages have known vulnerabilities. We recommend checking [security release notes](https://docs.spryker.com/docs/scos/user/intro-to-spryker/releases/release-notes/release-notes.html) under every release.
- Verify whether `composer` packages have known vulnerabilities. You can use the `./vendor/bin/console security:check` command to inspect third-party vulnerabilities.
- Verify whether `npm` packages have known vulnerabilities. You can use `npm audit` command to inspect third-party vulnerabilities.

## Exceptions and debug mode

Make sure that, in your production environment, the debugging mode is disabled, and exceptions are not shown.


Debug mode is configured with the following:
- `ApplicationConstants::ENABLE_APPLICATION_DEBUG`
- `ShopApplicationConstants::ENABLE_APPLICATION_DEBUG`
- `GlueApplicationConstants::GLUE_APPLICATION_REST_DEBUG`

## Demo data

*Remove all the demo data from the environment*. A project should only use the real data that will be used after going live. Remove all the demo data that comes with Spryker, which includes demo and admin users. Demo admin users in a live shop pose a significant security risk for your project. Also, make sure to set strong passwords when creating new admin users.

## OAuth configuration

We recommend using environment variables to define security configuration. Example:

```php
$config[OauthConstants::PRIVATE_KEY_PATH] = getenv(&apos;SPRYKER_OAUTH_KEY_PRIVATE&apos;);
$config[OauthConstants::PUBLIC_KEY_PATH]
    = $config[OauthCryptographyConstants::PUBLIC_KEY_PATH]
    = getenv(&apos;SPRYKER_OAUTH_KEY_PUBLIC&apos;);
$config[OauthConstants::ENCRYPTION_KEY] = getenv(&apos;SPRYKER_OAUTH_ENCRYPTION_KEY&apos;) ?: null;
$config[OauthConstants::OAUTH_CLIENT_CONFIGURATION] = json_decode(getenv(&apos;SPRYKER_OAUTH_CLIENT_CONFIGURATION&apos;), true) ?: [];
```

## ACL configuration

Set up the ACL configuration according to your requirements and restrict access to sensitive data. For more information, see [ACL configuration](/docs/pbc/all/merchant-management/{{site.version}}/marketplace/marketplace-merchant-portal-core-feature-overview/persistence-acl-configuration.html).


## Backend GATEWAY protection

Gateway is used for communication between the frontend and the backend. In most cases, it doesn&apos;t expect any communication from the internet. To protect this endpoint, you need to extend `deploy.{project}-{env}.yml` as follows:
1. Add backend auths:

```yaml
x-backend-auth: &amp;backend-auth
  &lt;&lt;: *real-ip
  auth:
    engine: whitelist
    include:
      -  128.01.01.01/32 #YVES/GLUE storefront IP, need to be requested from support

```

2. Add this auth to gateway endpoints:

```yaml
backgw:
    application: backend-gateway
    endpoints:
        {some-domain}.com:
            store: DE
            &lt;&lt;: *backend-auth # add this row to your gateway config
            primal: true
```

To verify the configuration, redeploy the environment and make sure that accessing the domain returns a 403 error, but ZED requests are going through.

## Summary

To sum up, the main points to keep the data secure are the following:

- Educate: Learn and spread [OWASP guidelines](https://owasp.org/www-pdf-archive/OWASP_SCP_Quick_Reference_Guide_v2.pdf) in your team.
- Check the presence of security-related HTTP headers.
- Check cookie settings.
- Configure TLS.
- Secure the Back Office.
- Check the Spryker configuration and change default authentication parameters like users and passwords.
- Protect your Backend GATEWAY from direct access.
- Keep systems and applications up to date.
- Make sure that exceptions are not shown and debug mode is disabled on production.
- Make sure that the keys data is taken from secure environment variables and is not embedded into the configuration files.
</description>
            <pubDate>Tue, 28 Jul 2026 12:32:55 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/guidelines/security-guidelines.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/guidelines/security-guidelines.html</guid>
            
            
        </item>
        
        <item>
            <title>OpenTelemetry instrumentation</title>
            <description>This document describes how to configure and instrument application for OpenTelemetry (OTel). It contains a brief overview of basic OTel concepts, but reading [OpenTelemetry documentation](https://opentelemetry.io/docs/concepts/) first is recommended.

## Convention

The current implementation version (1.20.0) follows [OpenTelemetry Semantic Conventions 1.38.0](https://github.com/open-telemetry/semantic-conventions/blob/v1.38.0/docs/README.md).

## Trace

A trace represents a single transaction. It has a unique ID and all spans are related to it. Each trace has a name that is defined automatically or can be changed as described in [Integration](#integration).

## Span

A span represents a unit of work or operation, similar to executing a single method. As the building blocks of traces, spans contain the following information:

- Name
- Parent span ID (empty for root spans)
- Start and End Timestamps
- Span Context
- Attributes
- Span Events
- Span Links
- Span Status



## Hook

A hook is a function that executes a closure before and after method execution, providing a way to instrument code without modifying it directly.
When you execute a method, a `pre` closure is executed and opens a span. After the method is executed, a `post` closure is executed and caught. Then, missing attributes are added and the span is closed.

All hooks that are autogenerated or provided by a library, such as Propel or key-value store (Redis or Valkey) hooks provided by Spryker, are registered automatically.

If you want to register a hook for a class, make sure the registration is executed before the method you want to instrument is called.

&lt;details&gt;
  &lt;summary&gt;Hook example&lt;/summary&gt;

```php
&lt;?php
\OpenTelemetry\Instrumentation\hook(
                class: MyClass::class, //Class name that should be instrumented.
                function: &apos;methodName&apos;, //Method of this class. It can be even a private method.
                pre: static function (
                $instance, //Instance of the MyClass
                array $params, //Incoming method parameters.
                string $class, //Class name as a string
                string $function, //Method name
                ?string $filename, //Actual file name that is executed
                ?int $lineno //Number of the line where this method is triggered
                ) {
                    //Context is used to keep all spans connected. In this case this is a parent span.
                    $context = \OpenTelemetry\Context\Context::getCurrent();

                    $span = \Spryker\Shared\OpenTelemetry\Instrumentation\CachedInstrumentation::getCachedInstrumentation()
                        -&gt;tracer()
                        -&gt;spanBuilder(&apos;ModuleName-MyClass::methodName&apos;)//Span name can be not unique, but it would easier to make it such if you want to find it easily.
                        -&gt;setParent($context)//Here parent span is attached to the current span.
                        -&gt;setAttribute(\OpenTelemetry\SemConv\TraceAttributes::CODE_FUNCTION, $function)//You can attach almost everything as a param as long as the value is scalar. Null value means that attribute will be omitted.
                        -&gt;setAttribute(\OpenTelemetry\SemConv\TraceAttributes::CODE_NAMESPACE, $class)
                        -&gt;setAttribute(\OpenTelemetry\SemConv\TraceAttributes::CODE_FILEPATH, $filename)
                        -&gt;setAttribute(\OpenTelemetry\SemConv\TraceAttributes::CODE_LINENO, $lineno)
                        -&gt;startSpan();

                    //Here span is attached to the global context
                    \OpenTelemetry\Context\Context::storage()-&gt;attach($span-&gt;storeInContext($context));
                },

                post: static function (
                $instance,
                array $params,
                $returnValue, //Result of the method execution.
                ?\Throwable $exception //Exception if one was thrown during the execution
                ) {
                    $scope = \OpenTelemetry\Context\Context::storage()-&gt;scope();

                    if (null === $scope) {
                        return;
                    }

                    //Here you can just check the $exception value. But in some cases you might want to take if from other places. E.g. if method that thrown exception was not instrumented, parent span should still have it.
                    $error = error_get_last();

                    if (is_array($error) &amp;&amp; in_array($error[&apos;type&apos;], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE], true)) {
                        $exception = new \Exception(
                            &apos;Error: &apos; . $error[&apos;message&apos;] . &apos; in &apos; . $error[&apos;file&apos;] . &apos; on line &apos; . $error[&apos;line&apos;]
                        );
                    }

                    $scope-&gt;detach();
                    $span = \Spryker\Service\Opentelemetry\Instrumentation\Span\Span::fromContext($scope-&gt;context());

                    if ($exception !== null) {
                        $span-&gt;recordException($exception);//Exception will be attached as an event into the span.
                        $span-&gt;setAttribute(&apos;error_message&apos;, $exception-&gt;getMessage());
                        $span-&gt;setAttribute(&apos;error_code&apos;, $exception-&gt;getCode());
                    }

                    //Status code adds some visibility. Error status code will mark your span as a one with an error for easier navigation.
                    $span-&gt;setStatus($exception !== null ? \OpenTelemetry\API\Trace\StatusCode::STATUS_ERROR : \OpenTelemetry\API\Trace\StatusCode::STATUS_OK);

                    //Span ends and sent into a span processor to be validated and prepared for exporting.
                    $span-&gt;end();
                }
            );
```

&lt;/details&gt;

## Collector

Collector collects traces and sends them to a monitoring platform. Traces are sent to collector after that request is sent so it doesn&apos;t affect response time. Collector operates separately from the application and should be set up by a Cloud engineer or you can add one for you local setup yourself.

## Integration

Run the latest version of the script from the [Installer repo](https://github.com/spryker/opentelemetry-installer/).

If you want to integrate manually, the following sections describe all steps of the scripts.

Be advised that Opentelemetry integration works with PHP 8.2 and above. if you are using older versions you must upgrade your PHP version first.

### Install required packages

OTel provides instrumentation via packages that can be installed to register hooks automatically. If you want to instrument additional parts of an application, such as Symfony code, you can install respective packages from [Registry](https://opentelemetry.io/ecosystem/registry/?language=php) or other sources.

{% info_block warningBox %}
Install third-party packages at your own risk.
{% endinfo_block %}


The [spryker/opentelemetry](https://packagist.org/packages/spryker/opentelemetry) package covers the essential parts of the integration:
- The entry point for instrumentation
- Plugin to wire in your monitoring service
- A console command to generate [hooks](https://opentelemetry.io/docs/zero-code/php/#how-it-works) for project&apos;s code, which creates spans automatically
- Instrumentation of Propel, key-value store (Redis or Valkey), ElasticSearch, RabbitMQ, and Guzzle calls

### Optional: Install the Monitoring module

The Monitoring module enables you to add custom attributes and events, change trace names during the request execution, and add exceptions to the root span for visibility.

You can get the Monitoring module from the [Packagist](https://packagist.org/packages/spryker/monitoring).

Install the module and wire the Monitoring plugin.

```php
&lt;?php

namespace Pyz\Service\Monitoring;

use Spryker\Service\Monitoring\MonitoringDependencyProvider as SprykerMonitoringDependencyProvider;
use Spryker\Service\Opentelemetry\Plugin\OpentelemetryMonitoringExtensionPlugin;

class MonitoringDependencyProvider extends SprykerMonitoringDependencyProvider
{
    /**
     * @return array&lt;\Spryker\Service\MonitoringExtension\Dependency\Plugin\MonitoringExtensionPluginInterface&gt;
     */
    protected function getMonitoringExtensions(): array
    {
        return [
            new OpentelemetryMonitoringExtensionPlugin(),
        ];
    }
}

```


You can call methods from Monitoring service, and they will be translated to OTel actions. Some methods act as placeholders because they&apos;re are not implemented in OTel–for example, `\Spryker\Service\Opentelemetry\Plugin\OpentelemetryMonitoringExtensionPlugin::markStartTransaction()`.



### Wire a console command

Spryker is a large application, so manually creating all hooks is impractical. The `OpentelemetryGeneratorConsole` command automates hook generation for classes you want to cover with spans.

```php
&lt;?php

namespace Pyz\Zed\Console;

...
use Spryker\Zed\Opentelemetry\Communication\Plugin\Console\OpentelemetryGeneratorConsole;
...

class ConsoleDependencyProvider extends SprykerConsoleDependencyProvider
{
    protected function getConsoleCommands(Container $container): array
    {
        $commands = [
            ...
            new OpentelemetryGeneratorConsole(),
            ...
        ];

        return $commands;
    }
}

```

Wire this console command into your install script to run on every deployment during the base container image build. Place it last in the `build` section, ensuring it runs after all code modifications and generation.

```yaml
sections:
    build:
        generate-open-telemetry:
            command: &apos;vendor/bin/console open-telemetry:generate&apos;
```

### Hooks generation configuration

You can control instrumentation by configuring specific methods.

`\Spryker\Zed\Opentelemetry\OpentelemetryConfig::getExcludedDirs()` defines directories to exclude from instrumentation. For example, you don&apos;t need spans from infrastructure code in traces. Several directories are excluded by default, review them in the module&apos;s vendor directory if you need to include any.  

```php
class OpentelemetryConfig extends AbstractBundleConfig
{
    // Traces for Monitoring module, OTel module and tests in existing module are not relevant for the monitoring, so those will be excluded. In the actual implementation you can see more directories.
    public function getExcludedDirs(): array
    {
        return [
        ...
            &apos;Monitoring&apos;,
            &apos;OpenTelemetry&apos;,
            &apos;tests&apos;,
        ...
        ];
    }
}
```

`\Spryker\Zed\Opentelemetry\OpentelemetryConfig::getExcludedSpans()` defines spans by name to exclude from instrumentation. This may be useful when spans you want to include and those you want to keep are in the same directory.

```php
class OpentelemetryConfig extends AbstractBundleConfig
{
    // In this example a span with a &apos;User-UserFacade::isSystemUser&apos; name will be not generated as it&apos;s not relevant for our traces, but in the same time it&apos;s called a lot of times during the request
    public function getExcludedSpans(): array
    {
        return [
            ...
            &apos;User-UserFacade::isSystemUser&apos;,
            ...
        ];
    }

}
```

`\Spryker\Zed\Opentelemetry\OpentelemetryConfig::getPathPatterns()` defines the path patterns where the console command should search for classes to instrument with hooks.

By default, all Spryker directories and the `Pyz` namespace are covered at the project level. To prevent unnecessary spans, avoid instrumenting autogenerated code such as Transfers.


```php
class OpentelemetryConfig extends AbstractBundleConfig
{
    public function getPathPatterns(): array
    {
        return [
            &apos;#^vendor/spryker/[^/]+/.*/.*/(Zed|Shared)/.*/(?!Persistence|Presentation)[^/]+/.*#&apos;,
            &apos;#^vendor/spryker/[^/]+/Glue.*#&apos;,
            &apos;#^vendor/spryker(?:/spryker)?-shop/[^/]+/.*#&apos;,
            &apos;#^vendor/spryker-eco/[^/]+/.*#&apos;,
            &apos;#^src/Pyz/.*#&apos;,
        ];
    }

}
```

`\Spryker\Zed\Opentelemetry\OpentelemetryConfig::getOutputDir()` specifies the directory where generated hooks are stored. By default, they&apos;re placed in `src/Generated/OpenTelemetry/Hooks/`. The `classmap.php` file, which is used to autoload hook files, is also added to this directory.


```php
class OpentelemetryConfig extends AbstractBundleConfig
{
    public function getOutputDir(): string
    {
        return APPLICATION_SOURCE_DIR . &apos;/Generated/OpenTelemetry/Hooks/&apos;;
    }

}
```

`\Spryker\Zed\Opentelemetry\OpentelemetryConfig::areOnlyPublicMethodsInstrumented()` defines which methods are instrumented. By default, hooks are generated only for public methods in regular classes and for all methods in Controller classes.

```php
class OpentelemetryConfig extends AbstractBundleConfig
{
    public function areOnlyPublicMethodsInstrumented(): bool
    {
        return true;
    }

}
```

`\Spryker\Zed\Opentelemetry\OpentelemetryConfig::getCriticalClassNamePatterns()` identifies spans as `critical` to prioritize them during sampling. By default, Controllers and Facades are included. This configuration doesn&apos;t use regex but matches based on a substring within the class name.

```php
class OpentelemetryConfig extends AbstractBundleConfig
{
    public function getCriticalClassNamePatterns(): array
    {
        return [
            &apos;Facade&apos;,
            &apos;Controller&apos;,
        ];
    }

}
```

### Enable PHP extensions

Hook processing requires specific PHP extensions. Use the `spryker/php:8.3` or `spryker/php:8.4` image and enable the `otel` extension in your deploy file:

```yaml
namespace: spryker-otel
tag: &apos;dev&apos;

environment: docker.dev
image:
    tag: spryker/php:8.4
    php:
        enabled-extensions:
            - otel
```

The blackfire extension conflicts with opentelemetry, so avoid using both simultaneously.

Don&apos;t use `newrelic` or `blackfire` extensions simultaneously with the `opentelemetry` extension simultaneously to avoid conflicts and broken traces. For more details, see [Conflicting extensions](https://github.com/open-telemetry/opentelemetry-php-instrumentation?tab=readme-ov-file#conflicting-extensions).

### SMI Logs integration

SMI Log integration is not using Opentelemetery&apos;s log provider, so there is no need to gather additional logs. Instead, Spryker enriches existing logs with trace and span IDs, so you can easily correlate logs and traces in SMI.
The OpenTelemetry log processor plugin adds trace and span IDs to log records, enabling correlation between logs and traces in SMI. To set up the integration, follow these steps:

#### Step 1 — Update `spryker/opentelemetry` to version 1.20.0

The `OpentelemetryLogProcessorPlugin` is introduced in `spryker/opentelemetry` **1.20.0**. Update the constraint in `composer.json` and install:

```bash
composer require spryker/opentelemetry:&quot;^1.20.0&quot; --update-with-dependencies
```

Verify the installed version:

```bash
composer show spryker/opentelemetry | grep versions
```

---

#### Step 2 — Make sure that you are using Log module

The OpenTelemetry log processor plugin is designed to work with Spryker&apos;s Log module. If you haven&apos;t installed it yet, add it to your project:

```bash
composer require spryker/log:&quot;^3.0.0&quot;
```

or 

```bash
composer require spryker/log:&quot;^3.17.0&quot;
```

if you want to use [Audit Log](/docs/dg/dev/backend-development/audit-logs/audit-logs.html) feature.

{% info_block warningBox &quot;Check how you collect logs&quot; %}

Only log records processed by the Log module will be enriched with trace and span IDs. Any non log output that goes directly to the standard output or error won&apos;t have trace and span IDs, so it won&apos;t be correlated in the SMI.

{% endinfo_block %}

#### Step 3 — Register the plugin in the Zed log dependency provider

Open `src/Pyz/Zed/Log/LogDependencyProvider.php` and add the import and plugin registration in all three processor methods.

Add the import:

```php
use Spryker\Zed\Opentelemetry\Communication\Plugin\Log\OpentelemetryLogProcessorPlugin;
```

Register the plugin in `getLogProcessors()`, `getZedSecurityAuditLogProcessorPlugins()`, and `getMerchantPortalSecurityAuditLogProcessorPlugins()`:

```php
new OpentelemetryLogProcessorPlugin(),
```

---

#### Step 4 — Register the plugin in the Yves log dependency provider

Open `src/Pyz/Yves/Log/LogDependencyProvider.php` and add the import and plugin registration.

Add the import:

```php
use Spryker\Yves\Opentelemetry\Plugin\Log\OpentelemetryLogProcessorPlugin;
```

Register the plugin in `getProcessors()` and `getYvesSecurityAuditLogProcessorPlugins()`:

```php
new OpentelemetryLogProcessorPlugin(),
```

---

#### Step 5 — Register the plugin in the Glue log dependency provider

Open `src/Pyz/Glue/Log/LogDependencyProvider.php` and add the import and plugin registration.

Add the import:

```php
use Spryker\Glue\Opentelemetry\Plugin\Log\OpentelemetryLogProcessorPlugin;
```

Register the plugin in `getProcessors()`, `getGlueSecurityAuditLogProcessorPlugins()`, and `getGlueBackendSecurityAuditLogProcessorPlugins()`:

```php
new OpentelemetryLogProcessorPlugin(),
```

---

#### Step 6 — Update Jenkins PHP jobs to manage build symlinks

Some Jenkins jobs produce a high volume of builds. Over time, the accumulation of build directories makes it increasingly difficult for the log exporter to scan them, eventually blocking log export entirely. To prevent this, every job must update the `lastBuild` symlink to point to the media-mounted build path immediately before executing its command.

In `config/Zed/cronjobs/jenkins.php`, add the following `buildCommand()` helper and apply it to all job definitions using `array_map`:

```php
/**
 * Prepends symlink maintenance commands to every Jenkins job command.
 *
 * Some jobs produce a large number of builds. Without this, the growing
 * number of build directories makes log export increasingly slow and
 * eventually blocks it entirely. Recreating the lastBuild symlink on each
 * run keeps the directory structure clean and log export unaffected.
 *
 * @param string $jobCommand
 *
 * @return string
 */
 
function buildCommand(string $jobCommand): string
{
    return implode(&quot;\n&quot;, [
        &apos;rm -f &quot;${JENKINS_HOME}/jobs/${JOB_NAME}/builds/lastBuild&quot;&apos;,
        &apos;ln -s &quot;${JENKINS_HOME}/jobs/${JOB_NAME}/builds/${BUILD_NUMBER}&quot; &quot;${JENKINS_HOME}/jobs/${JOB_NAME}/builds/lastBuild&quot;&apos;,
        $jobCommand,
    ]);
}

// Apply to all jobs
$jobs = array_map(function (array $job): array {
    $job[&apos;command&apos;] = buildCommand($job[&apos;command&apos;]);

    return $job;
}, $jobs);
```

The `rm -f` removes any stale symlink without failing if it does not exist. The subsequent `ln -s` unconditionally creates a fresh symlink pointing to the media-mounted build directory. The actual job command runs last and is unaffected by the symlink operations.

---

#### Step 7 — Create a support ticket to enable SMI logs

The OpenTelemetry log processor only enriches log records. For the enriched logs to be ingested and visible in SMI, the SMI log pipeline must be enabled for your environment by the Spryker Cloud team.

Create a support ticket for that.

## Sampling

Sampling is a mechanism to reduce the number of spans sent with traces. It&apos;s used in many systems but in different implementations. For an example of how sampling is implemented in New Relic, see [New Relic&apos;s tracing documentation](https://docs.newrelic.com/docs/distributed-tracing/concepts/how-new-relic-distributed-tracing-works/).

With the default New Relic setup, some traces may not reach New Relic&apos;s backend. However, in the OTel implementation, traces must not be missed because they contain critical event data, such as errors. This ensures that all traces are available in the backend, though some may not include detailed span data.

Spryker executes a big number of methods per request, many of them repeatedly. Because OTel uses PHP functions to open and close spans, excessive span creation can introduce unnecessary load on your application. To mitigate this, there&apos;re mechanisms to reduce the number of spans sent with traces.  

Sampling occurs three times during execution:  

1. Tracing sampling: Determines if the trace should be a root span only, without additional details
2. Opening span sampling: Decides whether to open a span before execution
3. Closing span sampling: Filters out extremely fast and successful spans upon closing because they likely hold little value


### Trace sampling

A detailed trace for every request or command execution is usually unnecessary. At minimum, a span should capture that the request occurred and whether it contained an error.

To do this, the request is checked during initialization. For HTTP requests, if the method is not `GET`, the trace is always detailed. If the method is `GET`, a random number between `0` and `1.0` is generated and compared against a configured probability. If the number is less than the configured probability, the trace includes spans. Otherwise, only a root span is recorded.

The same logic applies to console commands, but with a separate configuration value for finer control.  


### Opening span sampling  

On start, each span is checked whether it should be started using the algorithm similar to that used for trace sampling. The differences between algorithms are as follows:  

- A different configuration value is used for span sampling
- A random number is generated for each span
- Different span types have different sampling probabilities based on their criticality

If a span is not sampled, an empty span is created instead. Empty spans act as placeholders to maintain the trace structure and always appear in the trace.

### Closing span sampling

Fast spans without errors can be discarded from the trace. When a sampled span closes, its execution time and status are checked. If the span is successful and completes faster than a configured threshold, it&apos;s omitted and they don&apos;t appear in the trace. The threshold is configured in `OTEL_BSP_MIN_SPAN_DURATION_THRESHOLD` or `OTEL_BSP_MIN_CRITICAL_SPAN_DURATION_THRESHOLD`.


### Span criticality

Some spans are more relevant to users than others. To manage span sampling effectively, spans are categorized into three levels of criticality: `non-critical`, `regular`, and `critical`.

Each category uses different probability settings and execution time limits, which can be configured separately. For [closing span sampling](#closing-span-sampling), `regular` and `non-critical` spans are treated as the same type.


#### Critical spans


Spans that execute operations that communicate with other services or change the application&apos;s state should be marked as critical to have a higher chance of appearing of appearing in the trace

The following span types are critical by default:

- RabbitMQ spans  
- ElasticSearch spans  
- Key-value store (Redis or Valkey) spans  
- Guzzle spans (ignored by the sampling mechanism because they&apos;re required for Distributed Tracing)  
- Propel `INSERT`/`DELETE`/`UPDATE` calls  
- Hooks for classes configured in `\Spryker\Zed\Opentelemetry\OpentelemetryConfig::getCriticalClassNamePatterns()`  


#### Non-critical spans

Only Propel `SELECT` calls spans marked as `no_critical` because every request generates a lot of them, which can easily overflow a trace with useless information.


#### Regular spans

All other spans are considered as `regular`.


### Sampling configuration

You can adjust sampling values by changing environment variables. Increasing these values will generate more detailed traces but may also slow down your application because more spans will be sampled and sent to the collector.


| Variable Name                                 | Description                                                                                                                                                    | Default Value | Allowed range |
|-----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|---------------|
| OTEL_BSP_MIN_SPAN_DURATION_THRESHOLD          | Used in [Closing Span Sampling](#closing-span-sampling) to define a threshold in milliseconds. Spans with an execution time below this value are filtered out. | 20            | 0...100000    |
| OTEL_BSP_MIN_CRITICAL_SPAN_DURATION_THRESHOLD | Same as a previous one, but used only for critical spans.                                                                                                      | 10            | 0...100000    |
| OTEL_TRACES_SAMPLER_ARG                       | Defines the probability of a `regular` span to be sampled                                                                                                      | 0.1           | 0...1.0       |
| OTEL_TRACES_CRITICAL_SAMPLER_ARG              | Defines the probability of a `critical` span to be sampled                                                                                                     | 0.5           | 0...1.0       |
| OTEL_TRACES_NON_CRITICAL_SAMPLER_ARG          | Defines the probability of a `non critical` to be sampled                                                                                                      | 0.1           | 0...1.0       |
| OTEL_TRACE_PROBABILITY                        | Defines the probability of a web `GET` request trace being detailed.                                                                                           | 0.3           | 0...1.0       |
| OTEL_TRACE_PROBABILITY_NON_GET                | Defines the probability of a web non `GET` request trace being detailed.                                                                                       | 1.0           | 0...1.0       |
| OTEL_CLI_TRACE_PROBABILITY                    | Defines the probability of a console command trace to be detailed.                                                                                             | 0.5           | 0...1.0       |




### Additional configuration


| Variable Name                        | Description                                                                                                                                                                                                                       | Default Value     | Allowed range                                                                                                                                                                                                                 |
|--------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| OTEL_SERVICE_NAMESPACE               | Defines a service namespace used in resource definition                                                                                                                                                                           | spryker           | any string value                                                                                                                                                                                                              |
| OTEL_SERVICE_NAME_MAPPING            | A JSON object mapping application URLs to service names; used if no service name is provided via `MonitoringService::setApplicationName()`.                                                                                       | {}                | A valid JSON object where keys represent service names and values define URL patterns.                                                                                                                                        |
| OTEL_DEFAULT_SERVICE_NAME            | If no service name is provided and `OTEL_SERVICE_NAME_MAPPING` is not defined, this default name is used.                                                                                                                         | Default Service   | any valid string                                                                                                                                                                                                              |
| OTEL_BSP_SCHEDULE_DELAY              | Defines the delay in milliseconds before sending a batch of spans to the exporter. A higher value results in larger batches.                                                                                                      | 1000              | 0-100000000                                                                                                                                                                                                                 |
| OTEL_BSP_MAX_QUEUE_SIZE              | Defines the maximum number of spans that can be queued for processing in a single request.                                                                                                                                        | 2048              |                                                                                                                                                                                    |
| OTEL_BSP_MAX_EXPORT_BATCH_SIZE       | Defines the batch size for spans. Once this limit is reached, the batch is sent to the exporter.                                                                                                                                  | 512               | More than `0` and less than `OEL_BSP_MAX_QUEUE_SIZE`                                                                                                                                                                          |
| OTEL_SDK_DISABLED                    | If set to `true`, no traces are generated or sent to the backend. The default value is `true`; change only after the collector is up and running.                                                                                 | true              | Can be a boolean or a string representation of a boolean, such as `true` or `false`.                                                                                                                                          |
| OTEL_PHP_DISABLED_INSTRUMENTATIONS   | Disables specific parts of additional instrumentation. For example, to exclude all key-value store (Redis or Valkey) spans, set the value to `spryker_otel_redis`. Multiple instrumentation parts can be disabled by providing a comma-separated list.        |                   | `spryker_otel_redis`, `spryker_otel_elastica`, `spryker_otel_propel`, `spryker_otel_rabbitmq`, `spryker_otel_guzzle`, `all`, or a combination such as `spryker_otel_rabbitmq,spryker_otel_propel`.                            |


## Custom attributes

To add custom data to your traces, such as the logged-in user ID, the current store name, or any other request-specific information, add a custom attribute that appears in the root span for better visibility. There&apos;re attributes that are shipped by default, such as current store name or locale.

We recommend adding custom attributes through `MonitoringService::addCustomParameter()`. Alternatively, you can add them directly through `\Spryker\Service\Opentelemetry\OpentelemetryService::setCustomParameter`, which doesn&apos;t require the `Monitoring` module.

All attributes added via these services are included in the root span at the end of the request execution, so you can call them even after the response has been sent.




## Custom events

To add a custom event to your trace for backend logic configuration, you can trigger a custom event during execution.

You can add custom events via `\Spryker\Service\Monitoring\MonitoringService::addCustomEvent()`. Alternatively, add them directly though `\Spryker\Service\Opentelemetry\OpentelemetryService::addEvent()`, which doesn&apos;t require the Monitoring module.

Custom events are attached to the root span.


## Error handling


Default Spryker&apos;s Error Handler already executes `\Spryker\Service\Monitoring\MonitoringService::setError()`, so if you are using `Monitoring` module and default Error Handler - you are covered. But if you don&apos;t,  adjust your error handler accordingly.
This will add a error event into the root span and will change its status to the `error` one. Check this part during integration of OTel into your system.

The OTel integration catches all the exceptions thrown during a request or command execution and attaches them as events to the root span. These events will also appear in the span of the method that threw the exception, but only if a hook for that method exists.

To ensure error tracking, we recommend using `\Spryker\Service\Monitoring\MonitoringService::setError()` or `\Spryker\Service\Opentelemetry\OpentelemetryService::setError()` in your application&apos;s error handler.

The default error handler calls `\Spryker\Service\Monitoring\MonitoringService::setError()` to add error events to the root span and update its status to `error`. If you&apos;re using the `Monitoring` module with the default error handler, no additional configuration is needed. With a custom setup, adjust your error handler and verify this logic during the integration.


## Service name

Service names let you to filter traces by source. For example, you might want to analyze only Yves traces or Glue requests while excluding CLI commands from your Scheduler.

You can define a service name using `\Spryker\Service\Monitoring\MonitoringService::setApplicationName()` or `\Spryker\Service\Opentelemetry\OpentelemetryService::setResourceName()`.

All `MonitoringService` methods trigger a service name change. For example, calling `\Spryker\Service\Monitoring\MonitoringService::setError()` updates the service name.

Default name convention for service names: `APPLICATION-REGION_OR_STORE(APPLICATION.ENV)`

|PLACEHOLDER | DESCRIPTION|
| - | - |
| APPLICATION|  Application name, such as ZED, YVES, or GLUE. |
| REGION_OR_STORE|  Current store or region name, depending on whether Dynamic Store mode is enabled. |
| APPLICATION_ENV | Environment name from the deploy file. |

You can change any of these values using `MonitoringService`, or override the service name entirely using `OpentelemetryService`.

If no service name is explicitly set, the integration first checks `OTEL_SERVICE_NAME_MAPPING`, attempting to determine a service name based on the URL or CLI binary filename. If no matches found, it falls back to `OTEL_DEFAULT_SERVICE_NAME` from the environment configuration.




## Trace name

The trace name (or root span name) shows which request or command was executed.

Default behavior:
- For web requests: The default trace name includes the HTTP method and route name
- For command executions: The command name is used

Spryker automatically adjusts the trace name for web requests to reflect the route name. However, if you prefer a different naming convention or don&apos;t use the `Monitoring` module, you can define it manually using `\Spryker\Service\Opentelemetry\OpentelemetryService::setRootSpanName()` or `\Spryker\Service\Monitoring\MonitoringService::setTransactionName()`.

If no name was provided and no route was resolved, a fallback name is used. This name can be configured by adding the following to the regular configuration file:  

```php

$config[\Spryker\Shared\Opentelemetry\OpentelemetryConstants::FALLBACK_HTTP_ROOT_SPAN_NAME] = &apos;your_fallback_name&apos;;

//Can be used to show HTTP method in the root span name. Default is true.
$config[\Spryker\Shared\Opentelemetry\OpentelemetryConstants::SHOW_HTTP_METHOD_IN_ROOT_SPAN_NAME] = false;

```

## Recommendations


Tracing is resource-intensive and can slow down your application. Follow these recommendations to minimize performance impact:

- Minimize the number of generated spans per request:
  - OTel docs recommend keeping span count below 1000 per trace
  - Configure irrelevant spans to be skipped
  - Errors are processed even if a method is not instrumented because error events are attached to the root span

- Use sampling to reduce trace volume:
  - Full traces for every request are unnecessary in most cases
  - Refer to the [sampling configuration](#sampling-configuration) section to fine-tune trace collection

- Skip unnecessary traces:
  - You can control the probability of generating detailed traces using the `OTEL_TRACE_PROBABILITY` environment variable.  
  - Even if a detailed trace is skipped, a root span will still be created.  
  - Requests that modify the application state (`POST`, `DELETE`, `PUT`, `PATCH`) are always considered critical and will be fully processed.
</description>
            <pubDate>Tue, 28 Jul 2026 09:46:01 +0000</pubDate>
            <link>https://docs.spryker.com/docs/ca/dev/monitoring/spryker-monitoring-integration/opentelemetry-instrumentation.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/ca/dev/monitoring/spryker-monitoring-integration/opentelemetry-instrumentation.html</guid>
            
            
        </item>
        
        <item>
            <title>Add and configure cronjobs</title>
            <description>&lt;p&gt;This document shows how to add and configure cronjobs in Jenkins.&lt;/p&gt;
&lt;p&gt;We use &lt;a href=&quot;https://jenkins-ci.org/&quot;&gt;Jenkins&lt;/a&gt; for cronjob scheduling. Compared to Crontab, there are several benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Jobs are queued and can be manually executed.&lt;/li&gt;
&lt;li&gt;Job definitions are under version control and can be changed by any developer.&lt;/li&gt;
&lt;li&gt;Console output is available for debugging.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;add-a-new-job-and-run-it&quot;&gt;Add a new job and run it&lt;/h2&gt;
&lt;p&gt;Jobs are defined in &lt;code&gt;config/Zed/cronjobs/jenkins.php&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This file contains an array defining the jobs.&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Send emails every 10 minutes&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$jobs&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;s1&quot;&gt;&apos;name&apos;&lt;/span&gt;                  &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;send-mails&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;command&apos;&lt;/span&gt;               &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;$PHP_BIN vendor/bin/console mail:send-mail&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;schedule&apos;&lt;/span&gt;              &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;*/10 * * * *&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;enable&apos;&lt;/span&gt;                &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s1&quot;&gt;&apos;stores&apos;&lt;/span&gt;                &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;DE&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;FR&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;To import this configuration to Jenkins, run &lt;code&gt;vendor/bin/console scheduler:setup&lt;/code&gt;. In a production environment, this is part of the default Normal deployment process.&lt;/p&gt;
&lt;section class=&apos;info-block info-block--error&apos;&gt;&lt;i class=&apos;info-block__icon icon-error&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;
&lt;p&gt;When you remove jobs from &lt;code&gt;config/Zed/cronjobs/jenkins.php&lt;/code&gt;, the application cannot detect the removal, so the removed jobs stay in Jenkins in their last state.&lt;/p&gt;
&lt;p&gt;To stop them, do so manually or through a support request.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;h2 id=&quot;cronjob-configuration&quot;&gt;Cronjob configuration&lt;/h2&gt;
&lt;p&gt;For each job you can define several configurations:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;KEY&lt;/th&gt;
&lt;th&gt;TYPE&lt;/th&gt;
&lt;th&gt;PURPOSE&lt;/th&gt;
&lt;th&gt;✓&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;name&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Name of the job.&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;command&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;The &lt;a href=&quot;/docs/dg/dev/backend-development/console-commands/implement-console-commands.html&quot;&gt;console command&lt;/a&gt; that is executed.&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;schedule&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Expression that defines the job schedule (how often the job runs). The schedule string is compatible with the cronjob schedule definition—for example, &lt;code&gt;0 * * * *&lt;/code&gt; means run once each hour at minute 00. If the environment is in development, return an empty string—on the development environment, cronjobs run manually only.&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;enable&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Enable or disable jobs.&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stores&lt;/td&gt;
&lt;td&gt;array&lt;/td&gt;
&lt;td&gt;An array of stores where the job is executed.&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;section class=&apos;info-block info-block--error&apos;&gt;&lt;i class=&apos;info-block__icon icon-error&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;
&lt;p&gt;When you do not use Jenkins for job scheduling, there is no locking between concurrently running commands.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
</description>
            <pubDate>Tue, 28 Jul 2026 09:22:58 +0000</pubDate>
            <link>https://docs.spryker.com/docs/dg/dev/backend-development/cronjobs/add-and-configure-cronjobs.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/dg/dev/backend-development/cronjobs/add-and-configure-cronjobs.html</guid>
            
            
        </item>
        
        <item>
            <title>Vertex</title>
            <description>![vertex-hero](https://spryker.s3.eu-central-1.amazonaws.com/docs/pbc/all/tax-management/vertex/vertex.md/vertex-hero.png)

The Spryker Vertex module, based on the *Vertex O Series*, performs automatic, near-real-time tax calculations at the point of purchase while accounting for the following:

- Tax rates in each state, county, and city.
- Laws, rules, and jurisdiction boundaries.
- Special circumstances like tax holidays and product exemptions.

For more information about how Vertex calculates taxes, see the [Vertex O Series website](https://www.vertexinc.com/solutions/products/vertex-indirect-tax-o-series).

The Spryker Vertex module offers the following features that are worth considering when comparing it to the default Spryker [Tax Management capability](/docs/pbc/all/tax-management/latest/tax-management.html):

- *Configure Vertex in Spryker*: Add your Vertex configurations, including your company code, in `config/Shared/config_default.php` to connect your Spryker project to Vertex. Alternatively, you can manage every Vertex setting from the Back Office, per global or store scope, and choose the tax provider with the **Taxes** &gt; **Tax Provider** switch. For details, see [Configure Vertex in the Back Office](/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/install-vertex/configure-vertex-in-the-back-office.html).
- *Tax determination and calculation*: View tax estimates during checkout and calculated taxes before generating an invoice. This feature works across all regions, including countries where taxes are included in the price.
- *Discounts Support*: The Vertex module uses both the discount and the amount paid by the customer, sending this information to Vertex for tax calculation and estimation.
- *Manage tax exemptions*: Configure your project to exclude tax-exempt customers using the Vertex module.
- *View invoice reports in Vertex dashboard*: The Vertex module allows customers to send invoice reports for paid orders from Spryker to Vertex. Customers can opt out of sending invoices to Spryker if they choose.
- *Support for refunds*: When order items are returned, refunded, or a paid order is canceled, the Vertex module updates the tax report in Vertex for accurate reporting and compliance.
- *Failover Solution*: Store owners and marketplace operators can manage refunds and ensure accurate tax reporting even during downtime.
- *Supported Product Types*: The integration currently supports tax calculation only for items/products created using Spryker Product capabilities.
- *Application of custom tax rules to products*: You can implement custom tax rules to accommodate unique product categorizations or specific tax regulations that apply to your business. The Vertex Integration provides a means for taxes to be calculated using these rules.

## Supported Use Cases and Business Models

1. Tax Calculation in Regions where taxes are excluded from prices. For example, in the US and Canada.
2. Tax Calculation in Regions where taxes are included in the price. For example, in the EU.
3. Marketplace: Every line item sent from Spryker to Vertex includes the customer&apos;s shipping address and the merchant&apos;s warehouse address, which Vertex uses for tax calculation.
4. Support for Delivery Terms: Vertex allows customers to set delivery terms within their dashboard, which are used in tax calculation. This is especially important for cross-border transactions when the seller wants to use the customer&apos;s location to determine the applicable tax rate.
5. Inclusion of Shipping Tax in the Total Tax Calculated: Spryker sends the selected shipping method to Vertex. The `delivery-method-key` set in Spryker is used for this purpose. Projects must ensure this is mapped correctly inside the Vertex dashboard by following the steps below:
   - In Vertex you create a Taxability Driver with the same value from Spryker
   - In Vertex you create a Taxability Mapping for the driver to one of Vertex&apos;s defined Delivery Charges

The following diagram demonstrates the flow of the Vertex integration:

![vertex-flow](https://spryker.s3.eu-central-1.amazonaws.com/docs/pbc/all/tax-management/vertex/vertex.md/vertex-flow.png)

## How Vertex calculates taxes for different countries

The Vertex module calculates taxes based on the tax rules and rates of the country where the product is shipped. The Vertex app uses the shipping address to determine the tax rate.

In some cases, the Vertex module can&apos;t calculate taxes and returns a 0 tax rate. For example, when a seller is located in EU, and the buyer is located in the US.

So, make sure your project has a logic for such cases. For example, when a buyer selects a shipping address different from the project&apos;s default tax region or country, a warehouse address in the respective region needs to be used.


## Product Class Code

The Product Class Code is used to represent groups or categories of products or services with identical taxability. By default, Spryker Product SKU is sent as `LineItems[].product.value` and `LineItems.lineItemId`. The Vertex module does not create any Vertex Tax Categories.

### Item Flexible Fields

Item Flexible Fields are optional fields provided by a project. They are needed for the customization of tax calculation. Flexible Fields are supported by the Vertex module, and whether or not to use them is a business decision.

## Freight tax for shipment

Spryker doesn&apos;t support freight shipment in terms of big packaging support; but calculation of taxes for shipping prices is supported.

## Sending invoices to Vertex through OMS

The Spryker OMS transition command is used as an execution point to send a full order with all existing and custom fields provided by the project. The results will be visible in the Invoice Tax Details report.



## Next steps

[Integrate Vertex](/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/install-vertex/integrate-vertex.html)
</description>
            <pubDate>Fri, 24 Jul 2026 10:10:26 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/vertex.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/vertex.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

{% info_block infoBox &quot;Manage settings in the Back Office&quot; %}

Instead of environment variables, you can manage every Vertex setting from the Back Office **Configuration**, per global or store scope, and choose the tax provider with a **Taxes** &gt; **Tax Provider** switch. This is opt-in and backward compatible. For details, see [Configure Vertex in the Back Office](/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/install-vertex/configure-vertex-in-the-back-office.html).
If you decide so, skip this section.

{% endinfo_block %}

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

{% info_block warningBox %}
If you decided to use the Back Office Configuration, skip this section.
{% endinfo_block %}

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(array $configurationScopeTransfers = []): bool
    {
        return true;
    }

    public function isInvoicingEnabled(array $configurationScopeTransfers = []): 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. 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.

## 7. 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(),
    ];
}
```

## 8. 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 %}
```

## 9. 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>Fri, 24 Jul 2026 10:10:26 +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>Integrate Vertex Validator</title>
            <description>To integrate Vertex Validator, take the following steps.

Registers the `POST /tax-id-validate` Glue REST API endpoint that validates a customer&apos;s Tax Identification Number (VAT ID) against a given country code via the Vertex Taxamo service. This is useful for B2B storefronts where customers must provide a valid VAT ID during checkout or address management to qualify for tax-exempt or reverse-charge transactions within the EU.

## 1. Install required modules

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

## 2. Add glossary keys

1. Add the following keys to your existing glossary file:

&lt;details&gt;
&lt;summary&gt;Click to view all glossary keys&lt;/summary&gt;

```csv
key,translation,locale
vertex.tax-number-country-blocked,Dieses Land ist in den Einstellungen blockiert.,de_DE
vertex.tax-number-validation-not-available,Die Steuernummer konnte nicht durch den Dienst überprüft werden.,de_DE
vertex.tax-number-ignored,&quot;Die Steuernummer wurde ignoriert, da Nummern für diese Region, dieses Land oder diesen Verkäufer blockiert wurden.&quot;,de_DE
vertex.tax-number-syntax-valid,Das Format der Steuernummer ist korrekt. Es wurden jedoch keine weiteren Prüfungen durchgeführt.,de_DE
vertex.tax-number-syntax-invalid,Das Format der Steuernummer ist ungültig.,de_DE
vertex.tax-number-considered-valid-in-domestic-country,Die Steuernummer ist im Heimatland des Verkäufers gültig.,de_DE
vertex.tax-number-valid-according-to-external-service,Die Steuernummer wurde erfolgreich bei der Steuerbehörde validiert.,de_DE
vertex.tax-number-invalid-according-to-external-service,Die Steuernummer wurde bei der Steuerbehörde überprüft und ist ungültig.,de_DE
vertex.tax-number-validation-requested-additional-interactions,&quot;Die Steuerbehörde verlangt zusätzliche Informationen (z. B. CAPTCHA), um die Nummer zu validieren.&quot;,de_DE
vertex.tax-number-service-temporarily-unavailable,Der Validierungsdienst ist vorübergehend nicht erreichbar.,de_DE
vertex.tax-number-syntax-considered-valid-but-not-verified,&quot;Das Format der Steuernummer ist korrekt, aber der Status konnte nicht bestätigt werden, da der externe Dienst nicht reagierte.&quot;,de_DE
vertex.tax-number-country-blocked,This county is blocked in the settings.,en_US
vertex.tax-number-validation-not-available,The service was not able to validate this number.,en_US
vertex.tax-number-ignored,&quot;The number is ignored because the settings have been changed to block numbers for this region, country or seller.&quot;,en_US
vertex.tax-number-syntax-valid,&quot;The syntax of the ID is valid. However, no further validations were done. In cases where a checksum is required, like for India, the ID is considered valid if the syntax is valid and the checksum is not configured.&quot;,en_US
vertex.tax-number-syntax-invalid,The syntax of the ID is invalid.,en_US
vertex.tax-number-considered-valid-in-domestic-country,The Tax ID is valid in the domestic country of the supplier.,en_US
vertex.tax-number-valid-according-to-external-service,The Tax ID has been validated against the Tax Authority&apos;s database of Tax IDs and is valid.,en_US
vertex.tax-number-invalid-according-to-external-service,The Tax ID has been validated against the Tax Authority&apos;s database of Tax IDs and is invalid.,en_US
vertex.tax-number-validation-requested-additional-interactions,&quot;The Tax Authority has requested additional parameters. For example, a country might require CAPTCHA validation and needs more information before they can validate the ID.&quot;,en_US
vertex.tax-number-service-temporarily-unavailable,Could not connect to validation service due to temporary unavailability of the service.,en_US
vertex.tax-number-syntax-considered-valid-but-not-verified,The Tax ID syntax is valid but the ID&apos;s status could not be verified by the external service. This occurs when the on-error settings is set to syntax-check and external service does not respond.,en_US
vertex.invalid-request-data,Invalid request data.,en_US
vertex.invalid-request-data,Ungültige Anfragedaten.,de_DE
vertex.tax-app-disabled,Tax service is disabled.,en_US
vertex.tax-app-disabled,Die Steueranwendung ist deaktiviert.,de_DE
vertex.tax-validator-unavailable,Tax Validator API is unavailable.,en_US
vertex.tax-validator-unavailable,Die Steuerprüfungs-API ist nicht verfügbar.,de_DE
vertex.validator-api-inactive,Unable to connect to Vertex Validator API: vertex app or tax id validation is inactive.,en_US
vertex.validator-api-inactive,Verbindung zur Vertex Validator API fehlgeschlagen: Die Vertex-Anwendung oder der Steuernummern-Prüfdienst ist nicht aktiv.,de_DE
vertex.request-failed,Request to Vertex API failed.,en_US
vertex.request-failed,Anfrage an die Vertex-API fehlgeschlagen.,de_DE
vertex.invalid-credentials,Invalid credentials.,en_US
vertex.invalid-credentials,Ungültige Anmeldeinformationen.,de_DE
```

&lt;/details&gt;

2. Import the updated glossary:

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

### 3. Register the Glue API plugin

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

This step is required only if you want to expose tax validation via the REST API.

{% endinfo_block %}

Add the Glue plugin to `src/Pyz/Glue/GlueApplication/GlueApplicationDependencyProvider.php`:

```php
&lt;?php

namespace Pyz\Glue\GlueApplication;

use Spryker\Glue\GlueApplication\GlueApplicationDependencyProvider as SprykerGlueApplicationDependencyProvider;
use SprykerEco\Glue\Vertex\Plugin\VertexTaxValidateIdResourceRoutePlugin;

class GlueApplicationDependencyProvider extends SprykerGlueApplicationDependencyProvider
{
    protected function getResourceRoutePlugins(): array
    {
        return [
            // ... other plugins
            new VertexTaxValidateIdResourceRoutePlugin(),
        ];
    }

}
```

## 4. Make sure that the tax ID validator is enabled

```php
namespace Pyz\Zed\Vertex;

use SprykerEco\Zed\Vertex\VertexConfig as SprykerEcoVertexConfig;

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

## 5. Use translations

To use translations, send requests with the `Accept-Language` header. For example, to use German translations, include the `Accept-Language: de` header.

## Next step

[Verify Vertex connection](/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/verify-vertex-connection.html)
</description>
            <pubDate>Fri, 24 Jul 2026 10:10:26 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/install-vertex/integrate-vertex-validator.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-validator.html</guid>
            
            
        </item>
        
        <item>
            <title>Configure Vertex in the Back Office</title>
            <description>&lt;p&gt;This document describes how to manage Vertex settings from &lt;strong&gt;Back Office&lt;/strong&gt; &amp;gt; &lt;strong&gt;Configuration&lt;/strong&gt; and how to select the tax provider per scope &lt;code&gt;store&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With this feature enabled, operators manage every Vertex setting from the Back Office, per &lt;strong&gt;global&lt;/strong&gt; or &lt;strong&gt;store&lt;/strong&gt; scope, instead of environment variables. A new &lt;strong&gt;Configuration&lt;/strong&gt; &amp;gt; &lt;strong&gt;Taxes&lt;/strong&gt; &amp;gt; &lt;strong&gt;Tax Provider&lt;/strong&gt; switch chooses between the built-in Spryker tax calculation and Vertex. Save-time validation prevents leaving the integration in a broken state.&lt;/p&gt;
&lt;section class=&apos;info-block &apos;&gt;&lt;i class=&apos;info-block__icon icon-info&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;&lt;div class=&quot;info-block__title&quot;&gt;Backward compatible&lt;/div&gt;
&lt;p&gt;This feature is opt-in and backward compatible. Until you &lt;a href=&quot;#1-enable-the-configuration-source&quot;&gt;enable it&lt;/a&gt;, Vertex keeps reading its configuration from environment variables, and nothing changes for existing integrations.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;spryker-eco/vertex&lt;/code&gt; version &lt;code&gt;1.5.0&lt;/code&gt; or later.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/install-vertex/integrate-vertex.html&quot;&gt;Integrate Vertex&lt;/a&gt; into your Spryker shop.&lt;/li&gt;
&lt;li&gt;Install the Configuration Management feature. The Vertex module depends on &lt;code&gt;spryker/configuration-extension&lt;/code&gt;, but it does not require the &lt;code&gt;spryker/configuration&lt;/code&gt; module that provides the Back Office UI and stores the values. If the Configuration Management feature is not installed yet, install it first. For details, see &lt;a href=&quot;/docs/dg/dev/integrate-and-configure/integrate-confguration-feature.html&quot;&gt;Install the Configuration Management feature&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;enable-the-configuration-source&quot;&gt;1. Enable the configuration source&lt;/h2&gt;
&lt;p&gt;The configuration source is gated behind the &lt;code&gt;isConfigurationModuleUsed()&lt;/code&gt; flag, which defaults to &lt;code&gt;false&lt;/code&gt; (environment-based behavior). To read values from the Back Office instead, override the flag in &lt;code&gt;src/Pyz/Shared/Vertex/VertexConfig.php&lt;/code&gt; to return &lt;code&gt;true&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Pyz\Shared\Vertex&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;SprykerEco\Shared\Vertex\VertexConfig&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SprykerEcoVertexConfig&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;VertexConfig&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SprykerEcoVertexConfig&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isConfigurationModuleUsed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&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;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;When the flag is &lt;code&gt;true&lt;/code&gt;, &lt;code&gt;SprykerEco\Zed\Vertex\VertexConfig&lt;/code&gt; reads values using &lt;a href=&quot;/docs/dg/dev/backend-development/configuration-management.html#reading-configuration-values&quot;&gt;getModuleConfig&lt;/a&gt; instead of from the &lt;code&gt;config/Shared/config_default.php&lt;/code&gt; constants.&lt;/p&gt;
&lt;h2 id=&quot;register-the-pre-save-validation-plugin&quot;&gt;2. Register the pre-save validation plugin&lt;/h2&gt;
&lt;p&gt;Register &lt;code&gt;VertexTaxProviderPreSavePlugin&lt;/code&gt; in &lt;code&gt;src/Pyz/Zed/Configuration/ConfigurationDependencyProvider.php&lt;/code&gt; to enable save-time validation:&lt;/p&gt;
&lt;div class=&quot;language-php highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;Pyz\Zed\Configuration&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\Configuration\ConfigurationDependencyProvider&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SprykerConfigurationDependencyProvider&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;SprykerEco\Zed\Vertex\Communication\Plugin\Configuration\VertexTaxProviderPreSavePlugin&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;ConfigurationDependencyProvider&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SprykerConfigurationDependencyProvider&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\ConfigurationExtension\Dependency\Plugin\ConfigurationValuePreSavePluginInterface&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;getConfigurationValuePreSavePlugins&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;c1&quot;&gt;// ... other plugins&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;VertexTaxProviderPreSavePlugin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;sync-the-configuration-schema&quot;&gt;3. Sync the configuration schema&lt;/h2&gt;
&lt;p&gt;Sync the configuration schema so the Vertex settings appear in the Back Office:&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 configuration:sync
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id=&quot;manage-vertex-settings-in-the-back-office&quot;&gt;4. Manage Vertex settings in the Back Office&lt;/h2&gt;
&lt;p&gt;After the sync, the settings are available under &lt;strong&gt;Configuration&lt;/strong&gt; in Integrations &amp;gt; Vertex. All settings are scoped to &lt;strong&gt;global&lt;/strong&gt; and &lt;strong&gt;store&lt;/strong&gt; by default.&lt;/p&gt;
&lt;h3 id=&quot;integrations-gt-vertex&quot;&gt;Integrations &amp;gt; Vertex&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Configuration&lt;/td&gt;
&lt;td&gt;Security URI&lt;/td&gt;
&lt;td&gt;Required, URL-validated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration&lt;/td&gt;
&lt;td&gt;Transaction calls URI&lt;/td&gt;
&lt;td&gt;Required, URL-validated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration&lt;/td&gt;
&lt;td&gt;Client ID&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration&lt;/td&gt;
&lt;td&gt;Client secret&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration&lt;/td&gt;
&lt;td&gt;Default taxpayer company code&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration&lt;/td&gt;
&lt;td&gt;Vendor code&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration&lt;/td&gt;
&lt;td&gt;Seller country code&lt;/td&gt;
&lt;td&gt;Optional, 2-letter ISO code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration&lt;/td&gt;
&lt;td&gt;Customer country code&lt;/td&gt;
&lt;td&gt;Optional, 2-letter ISO code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tax ID validation (Taxamo)&lt;/td&gt;
&lt;td&gt;Taxamo API URL&lt;/td&gt;
&lt;td&gt;URL-validated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tax ID validation (Taxamo)&lt;/td&gt;
&lt;td&gt;Taxamo token&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invoicing&lt;/td&gt;
&lt;td&gt;Submit Tax invoices to Vertex&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tax Assist&lt;/td&gt;
&lt;td&gt;Enable Tax Assist in Vertex&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;section class=&apos;info-block &apos;&gt;&lt;i class=&apos;info-block__icon icon-info&apos;&gt;&lt;/i&gt;&lt;div class=&apos;info-block__content&apos;&gt;&lt;div class=&quot;info-block__title&quot;&gt;Tax ID validation&lt;/div&gt;
&lt;p&gt;The Taxamo API URL and token are stored but consumed only when the tax ID validator is enabled. For details, see &lt;a href=&quot;/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/install-vertex/integrate-vertex-validator.html&quot;&gt;Integrate Vertex Validator&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
&lt;h3 id=&quot;taxes-gt-tax-provider&quot;&gt;Taxes &amp;gt; Tax Provider&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Label&lt;/th&gt;
&lt;th&gt;Values&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tax provider&lt;/td&gt;
&lt;td&gt;&lt;code&gt;spryker&lt;/code&gt; (default) or &lt;code&gt;vertex&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Selecting &lt;strong&gt;vertex&lt;/strong&gt; requires a complete Vertex configuration for that scope. This requirement is enforced at save time.&lt;/p&gt;
&lt;h2 id=&quot;configuration-source-and-precedence&quot;&gt;Configuration source and precedence&lt;/h2&gt;
&lt;p&gt;The active configuration source depends on the flag &lt;code&gt;Spryker\Shared\Vertex\VertexConfig::isConfigurationModuleUsed()&lt;/code&gt; or its project-level value:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Flag off (default):&lt;/strong&gt; getters read the legacy &lt;code&gt;VERTEX:*&lt;/code&gt; environment values from &lt;code&gt;config/Shared/config_default.php&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flag on:&lt;/strong&gt; getters resolve values per scope &lt;code&gt;store&lt;/code&gt; from the &lt;code&gt;spryker/configuration&lt;/code&gt;. Environment values from &lt;code&gt;config/Shared/config_default.php&lt;/code&gt; is ignored.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;save-time-validation&quot;&gt;Save-time validation&lt;/h2&gt;
&lt;p&gt;When you save a configuration, the following changes are blocked to prevent leaving the integration in a broken state:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Selecting Vertex while incomplete:&lt;/strong&gt; you cannot switch the tax provider to Vertex if Vertex is not fully configured for that scope. The error lists the missing fields.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Breaking an active configuration:&lt;/strong&gt; you cannot clear or remove a credential that would leave Vertex incomplete while Vertex is the selected provider for that scope. This applies to both edits and deletions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-scope breakage:&lt;/strong&gt; a change to the &lt;strong&gt;global&lt;/strong&gt; scope that would break any &lt;strong&gt;store&lt;/strong&gt; that has Vertex selected and inherits the global values is blocked. The error names the affected store.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Invalid URLs:&lt;/strong&gt; the Security URI, Transaction calls URI, and Taxamo API URL must be valid URLs, even when Vertex is not the selected provider.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next steps&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/verify-vertex-connection.html&quot;&gt;Verify Vertex connection&lt;/a&gt;&lt;/p&gt;
</description>
            <pubDate>Fri, 24 Jul 2026 10:10:26 +0000</pubDate>
            <link>https://docs.spryker.com/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/install-vertex/configure-vertex-in-the-back-office.html</link>
            <guid isPermaLink="true">https://docs.spryker.com/docs/pbc/all/tax-management/latest/base-shop/third-party-integrations/vertex/install-vertex/configure-vertex-in-the-back-office.html</guid>
            
            
        </item>
        
    </channel>
</rss>
