I want to show the original/list price and sale price (actual price) in my store as well as the discount percentage: N% off!

In Drupal 7, there is the excellent Commerce Price Savings Formatter module-- just install the module, set the prices, and you're done.

What can I do in Drupal 8? I have tried to use the built-in promotion feature in Commerce 2, but as far as I can tell the promotion UI can only be used to create coupons, not discounts that automatically apply to the relevant orders. (based on a reading of the documentation and experimenting with the module).


3 Answers 3


Easiest way for me was to create my own FieldFormatter for the price and make it extend Drupal\commerce_order\Plugin\Field\FieldFormatter\PriceCalculatedFormatter.

Once you did that, flush the cache of your website, go to the manage display of your Product variation type, select "My Custom price calculated formatter" for Price field and in the parameters, check "Apply promotions to the calculated price". And voilà!

Here is an example code for the custom FieldFormatter:


namespace Drupal\my_custom_module\Plugin\Field\FieldFormatter;

use CommerceGuys\Intl\Formatter\CurrencyFormatterInterface;
use Drupal\commerce\Context;
use Drupal\commerce\PurchasableEntityInterface;
use Drupal\commerce_order\AdjustmentTypeManager;
use Drupal\commerce_order\PriceCalculatorInterface;
use Drupal\commerce_price\Plugin\Field\FieldFormatter\PriceDefaultFormatter;
use Drupal\commerce_store\CurrentStoreInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\commerce_order\Plugin\Field\FieldFormatter\PriceCalculatedFormatter; // This is the FieldFormatter we want to extend

 * Plugin implementation of the 'my_custom_price_calculated_formatter' formatter.
 * @FieldFormatter(
 *   id = "my_custom_price_calculated_formatter",
 *   label = @Translation("My Custom price calculated formatter"),
 *   field_types = {
 *     "commerce_price"
 *   }
 * )
class MyCustomPriceCalculatedFormatter extends PriceCalculatedFormatter implements ContainerFactoryPluginInterface {

   * {@inheritdoc}
   * This is the function we want to extend
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = [];
    if (!$items->isEmpty()) {
      $context = new Context($this->currentUser, $this->currentStore->getStore(), NULL, [
        'field_name' => $items->getName(),
      /** @var \Drupal\commerce\PurchasableEntityInterface $purchasable_entity */
      $purchasable_entity = $items->getEntity();
      $adjustment_types = array_filter($this->getSetting('adjustment_types'));
      $result = $this->priceCalculator->calculate($purchasable_entity, 1, $context, $adjustment_types);
      $calculated_price = $result->getCalculatedPrice();
      $number = $calculated_price->getNumber();
      $currency_code = $calculated_price->getCurrencyCode();
      $options = $this->getFormattingOptions();

      // Check if there is any adjustment for the price
      // If so, get the BasePrice (without adjustment) and format it
      if(!empty($result->getAdjustments())) {
        $adjustments = $result->getAdjustments();
        foreach($adjustments as $single_adjustement) {
          // Get the discount percentage off
          // NB: there could be many other discounts applied but this example catches percentage discounts only
          if($single_adjustement->getType() == "promotion" && $single_adjustement->getPercentage() !== NULL) {
            $discount = $this->t('@discount off', ['@discount' => $single_adjustement->getPercentage() * 100 . '%']);
        $base_price = $result->getBasePrice();
        $base_price_number = $base_price->getNumber();
        $base_price_currency_code = $base_price->getCurrencyCode();
        $base_price_formatted = $this->currencyFormatter->format($base_price_number, $base_price_currency_code, $options);

      // This is our calculated price with the promotion
      $price_display = $this->currencyFormatter->format($number, $currency_code, $options);
      if(isset($base_price_formatted)) {
        // Set the base price (old-price), the percentage off and the calculated price within $price_display
        $price_display = '<div class="old-price">' . $base_price_formatted . '</div><div class="percentage-off">' . $discount . '</div><div class="new-price">' . $price_display . '</div>';

      $elements[0] = [
        '#markup' => $price_display,
        '#cache' => [
          'tags' => $purchasable_entity->getCacheTags(),
          'contexts' => Cache::mergeContexts($purchasable_entity->getCacheContexts(), [
            'languages:' . LanguageInterface::TYPE_INTERFACE,

    return $elements;

  • just wondering if 5 years on, if this is the way to go or not (goal being eg 'show full price as strikethrough' and 'show discounted price') Aug 25, 2023 at 1:30

I found a way to do this on my site by using Attributes. Each product variation has it's own MSRP attribute. Then on the display I chose Rendered Attribute as the display style, then I used some CSS to remove the border, the red asterisk, and made the variation unclickable. Also used CSS to put a red strike through it, and ultimately achieve exactly what I wanted. This is a method you would use if you have product variations. Just make sure when sorting the order of your variations, to make the MSRP field last. That way it won't show the other options, it only shows the MSRP related to the variation they are choosing. Works like a charm.


Here's a basic way to implement this in Commerce 2 if you don't need everything done automatically.

  1. Go to the "product type" admin page for the relevant product type and choose Manage fields.
  2. Add a price field, list price. Make sure to check the box for the right currency; I couldn't get the form to save without doing so.
  3. Add an integer field, set the min value 0 and the max value 100, and then the suffix as %.
  4. Use Views or a theme template to add some CSS classes to theme it nicely.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.