SyliusCon 2025 in Lyon
Join Us!
LogoLogo
🛣️ Roadmap💻 Sylius Demo💬 Community Slack
  • Sylius Documentation
  • Sylius Plugins
  • Sylius Stack
  • 📖Sylius Documentation
  • Organization
    • Sylius Team
  • Release Cycle
    • Backwards Compatibility Promise
  • Getting Started with Sylius
    • Installation
    • Basic Configuration
    • Shipping & Payment
    • First Product
    • Customizing the Shop
    • Customizing Business Logic
    • Using API
    • Installing Plugins
    • Deployment
    • Summary
  • The Book
    • Introduction to Sylius
    • Installation
      • System Requirements
      • Sylius CE Installation
        • Sylius CE Installation with Docker
      • ➕Sylius Plus Installation
      • Upgrading Sylius CE
      • Upgrading Sylius Plus
    • Architecture
      • Architecture Overview
      • Architectural Drivers
      • Resource Layer
      • State Machine
      • Translations
      • E-Mails
      • Contact
      • Fixtures
      • Events
    • Configuration
      • Channels
      • Locales
      • Currencies
    • Customers
      • Customer & ShopUser
      • ➕Customer Pools
      • AdminUser
      • Addresses
        • Countries
        • Zones
        • Addresses
        • Address Book
    • Products
      • Products
      • Product Reviews
      • Product Associations
      • Attributes
      • Pricing
        • B2B Pricing Engine
      • Catalog Promotions
      • Taxons
      • Inventory
      • ➕Multi-Source Inventory
      • Search
    • Carts & Orders
      • Orders
      • Cart flow
      • Taxation
      • Adjustments
      • Cart Promotions
      • Coupons
      • Payments
      • 🧩Invoices
      • Shipments
    • 🎨Frontend & Themes
    • Support
    • Contributing
      • Contributing Code
        • Submitting a Patch
        • ⚠️Security Issues
        • Coding Standards
        • Conventions
        • Sylius License and Trademark
      • Contributing Translations
      • Key Contributors
  • The Customization Guide
    • Customizing Models
      • How to add a custom model?
      • How to add a custom translatable model?
    • Customizing Forms
      • How to add a live form for a custom model?
    • Customizing Templates
    • Customizing Styles
    • Customizing Dynamic Elements
    • Customizing Validation
    • Customizing Menus
    • Customizing Translations
    • Customizing Flashes
    • Customizing State Machines
    • Customizing Grids
    • Customizing Fixtures
    • Customizing API
    • Customizing Serialization of API
    • Customizing Payments
      • How to integrate a Payment Gateway as a Plugin?
  • 🧑‍🍳THE COOKBOOK
    • How to resize images?
  • How to add one image to an entity?
  • How to add multiple images to an entity?
  • How to add a custom cart promotion action?
  • How to add a custom cart promotion rule?
  • How to add a custom catalog promotion action?
  • How to add a custom catalog promotion scope?
  • How to customize catalog promotion labels?
  • How to improve the performance of the catalog promotions?
  • How to add a custom shipping method rule?
  • Sylius 1.X Documentation
    • 📓Sylius 1.x Documentation
Powered by GitBook
LogoLogo

Developer

  • Community
  • Online Course

About

  • Team

© 2025 Sylius. All Rights Reserved

On this page
  • 1. Create the Price Calculator Service
  • Service Registration
  • FixedPriceCalculator Class
  • 2. Build the Admin Configuration Form
  • Service Registration
  • FixedPriceConfigurationType Form
  • ChannelBasedFixedPriceActionConfigurationType Form
  • 3. Configure Translations
  • Add Translation for Action Type
  • 4. Custom Validation (Optional)
  • ✅ Result

Was this helpful?

Edit on GitHub

How to add a custom catalog promotion action?

Sylius offers flexible catalog promotions that allow dynamic product price adjustments per sales channel. This guide shows you how to create a custom catalog promotion action that sets a fixed price for products based on the channel. You will integrate the following components:

  • Custom price calculator logic

  • Admin form integration

By the end of this guide, you will have a fully integrated catalog promotion action within the Sylius Admin Panel and API.


1. Create the Price Calculator Service

The price calculator determines the new price for each product variant per channel using your custom logic.

Service Registration

Register the price calculator service:

# config/services.yaml
services:
    App\Calculator\FixedPriceCalculator:
        tags:
            - { name: 'sylius.catalog_promotion.price_calculator', type: 'fixed_price' }

FixedPriceCalculator Class

Create the FixedPriceCalculator class to handle price calculations for each channel:

<?php

// src/Calculator/FixedPriceCalculator.php

namespace App\Calculator;

use Sylius\Bundle\CoreBundle\CatalogPromotion\Calculator\ActionBasedPriceCalculatorInterface;
use Sylius\Component\Core\Model\ChannelPricingInterface;
use Sylius\Component\Promotion\Model\CatalogPromotionActionInterface as BaseAction;

final class FixedPriceCalculator implements ActionBasedPriceCalculatorInterface
{
    public const TYPE = 'fixed_price';

    public function supports(BaseAction $action): bool
    {
        return $action->getType() === self::TYPE;
    }

    public function calculate(ChannelPricingInterface $channelPricing, BaseAction $action): int
    {
        $config = $action->getConfiguration();
        $channelCode = $channelPricing->getChannelCode();
        if (empty($config[$channelCode]['price'])) {
            return $channelPricing->getPrice();
        }

        $price = (int) $config[$channelCode]['price'];
        $min = $channelPricing->getMinimumPrice() > 0 ? $channelPricing->getMinimumPrice() : 0;

        return max($price, $min);
    }
}

2. Build the Admin Configuration Form

This form allows admins to set fixed prices per channel.

Service Registration

Register the admin form service:

# config/services.yaml

services:
    App\Form\Type\CatalogPromotionAction\ChannelBasedFixedPriceActionConfigurationType:
        tags:
            - { name: 'sylius.catalog_promotion.action_configuration_type', key: 'fixed_price' }
            - { name: 'form.type' }

FixedPriceConfigurationType Form

This form enables the admin to set a fixed price for each channel:

<?php

// src/Form/Type/FixedPriceConfigurationType.php

namespace App\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Sylius\Bundle\MoneyBundle\Form\Type\MoneyType;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\GreaterThan;

final class FixedPriceConfigurationType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('price', MoneyType::class, [
                'label' => 'Price',
                'currency' => $options['currency'],
                'constraints' => [
                    new NotBlank(['message' => 'Price must be set']),
                    new GreaterThan(['value' => 0, 'message' => 'Price must be greater than 0']),
                ],
            ]);
    }

    public function configureOptions($resolver): void
    {
        $resolver
            ->setRequired(['currency'])
            ->setAllowedTypes('currency', 'string');
    }

    public function getBlockPrefix(): string
    {
        return 'app_catalog_promotion_action_fixed_price_configuration';
    }
}

ChannelBasedFixedPriceActionConfigurationType Form

This form handles the configuration of fixed prices for each channel:

<?php

// src/Form/Type/CatalogPromotionAction/ChannelBasedFixedPriceActionConfigurationType.php

namespace App\Form\Type\CatalogPromotionAction;

use Symfony\Component\Form\AbstractType;
use Sylius\Bundle\CoreBundle\Form\Type\ChannelCollectionType;
use Sylius\Component\Core\Model\ChannelInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

final class ChannelBasedFixedPriceActionConfigurationType extends AbstractType
{
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'entry_type' => FixedPriceConfigurationType::class,
            'entry_options' => fn(ChannelInterface $channel) => [
                'label' => $channel->getName(),
                'currency' => $channel->getBaseCurrency()->getCode(),
            ],
        ]);
    }

    public function getParent(): string
    {
        return ChannelCollectionType::class;
    }
}

3. Configure Translations

Add the necessary translation labels for your custom action.

Add Translation for Action Type

In the translations/messages.en.yaml file, define the label for your fixed price action:

sylius:
    ui:
        fixed_price: 'Fixed Price'

4. Custom Validation (Optional)

Tip: For customizing validation rules (e.g., enforcing price constraints), refer to Sylius' Custom Validation Guide.


✅ Result

You can now select "Fixed Price" as a action type when creating or editing catalog promotions.

If your catalog promotion is not active, make sure the Messenger worker is active:

php bin/console messenger:consume main
PreviousHow to add a custom cart promotion rule?NextHow to add a custom catalog promotion scope?

Last updated 1 day ago

Was this helpful?