Show a sale price and discount percentage in Commerce 2 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).
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:

&lt;?php

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-&gt;isEmpty()) {
      $context = new Context($this-&gt;currentUser, $this-&gt;currentStore-&gt;getStore(), NULL, [
        'field_name' =&gt; $items-&gt;getName(),
      ]);
      /** @var \Drupal\commerce\PurchasableEntityInterface $purchasable_entity */
      $purchasable_entity = $items-&gt;getEntity();
      $adjustment_types = array_filter($this-&gt;getSetting('adjustment_types'));
      $result = $this-&gt;priceCalculator-&gt;calculate($purchasable_entity, 1, $context, $adjustment_types);
      $calculated_price = $result-&gt;getCalculatedPrice();
      $number = $calculated_price-&gt;getNumber();
      $currency_code = $calculated_price-&gt;getCurrencyCode();
      $options = $this-&gt;getFormattingOptions();

      // Check if there is any adjustment for the price
      // If so, get the BasePrice (without adjustment) and format it
      if(!empty($result-&gt;getAdjustments())) {
        $adjustments = $result-&gt;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-&gt;getType() == "promotion" &amp;&amp; $single_adjustement-&gt;getPercentage() !== NULL) {
            $discount = $this-&gt;t('@discount off', ['@discount' =&gt; $single_adjustement-&gt;getPercentage() * 100 . '%']);
          }
        }
        $base_price = $result-&gt;getBasePrice();
        $base_price_number = $base_price-&gt;getNumber();
        $base_price_currency_code = $base_price-&gt;getCurrencyCode();
        $base_price_formatted = $this-&gt;currencyFormatter-&gt;format($base_price_number, $base_price_currency_code, $options);
      }

      // This is our calculated price with the promotion
      $price_display = $this-&gt;currencyFormatter-&gt;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 = '&lt;div class="old-price"&gt;' . $base_price_formatted . '&lt;/div&gt;&lt;div class="percentage-off"&gt;' . $discount . '&lt;/div&gt;&lt;div class="new-price"&gt;' . $price_display . '&lt;/div&gt;';
      }

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

    return $elements;
  }

}