diff --git a/modules/order/src/Plugin/Commerce/Condition/PurchasedEntityConditionBase.php b/modules/order/src/Plugin/Commerce/Condition/PurchasedEntityConditionBase.php
index 7a51619a..6c65a2f3 100644
--- a/modules/order/src/Plugin/Commerce/Condition/PurchasedEntityConditionBase.php
+++ b/modules/order/src/Plugin/Commerce/Condition/PurchasedEntityConditionBase.php
@@ -4,13 +4,14 @@ namespace Drupal\commerce_order\Plugin\Commerce\Condition;
 
 use Drupal\commerce\EntityUuidMapperInterface;
 use Drupal\commerce\Plugin\Commerce\Condition\ConditionBase;
+use Drupal\commerce\Plugin\Commerce\Condition\PurchasableEntityConditionInterface;
 use Drupal\commerce\PurchasableEntityInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
-abstract class PurchasedEntityConditionBase extends ConditionBase implements ContainerFactoryPluginInterface {
+abstract class PurchasedEntityConditionBase extends ConditionBase implements PurchasableEntityConditionInterface, ContainerFactoryPluginInterface {
 
   /**
    * The entity type manager.
@@ -134,4 +135,23 @@ abstract class PurchasedEntityConditionBase extends ConditionBase implements Con
       in_array($purchasable_entity->uuid(), $this->configuration['entities'], TRUE);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getPurchasableEntityIds() {
+    return $this->entityUuidMapper->mapToIds($this->getPurchasableEntityType(), $this->configuration['entities']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPurchasableEntities() {
+    if ($entity_ids = $this->getPurchasableEntityIds()) {
+      $storage = $this->entityTypeManager->getStorage($this->getPurchasableEntityType());
+      $entities = $storage->loadMultiple($entity_ids);
+    }
+
+    return $entities ?? [];
+  }
+
 }
diff --git a/modules/product/src/Plugin/Commerce/Condition/OrderItemProduct.php b/modules/product/src/Plugin/Commerce/Condition/OrderItemProduct.php
index 9382f10c..b1a0106b 100644
--- a/modules/product/src/Plugin/Commerce/Condition/OrderItemProduct.php
+++ b/modules/product/src/Plugin/Commerce/Condition/OrderItemProduct.php
@@ -4,6 +4,7 @@ namespace Drupal\commerce_product\Plugin\Commerce\Condition;
 
 use Drupal\commerce\EntityUuidMapperInterface;
 use Drupal\commerce\Plugin\Commerce\Condition\ConditionBase;
+use Drupal\commerce\Plugin\Commerce\Condition\PurchasableEntityConditionInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
@@ -21,10 +22,17 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
  *   weight = -1,
  * )
  */
-class OrderItemProduct extends ConditionBase implements ContainerFactoryPluginInterface {
+class OrderItemProduct extends ConditionBase implements PurchasableEntityConditionInterface, ContainerFactoryPluginInterface {
 
   use ProductTrait;
 
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
   /**
    * Constructs a new OrderItemProduct object.
    *
@@ -43,6 +51,7 @@ class OrderItemProduct extends ConditionBase implements ContainerFactoryPluginIn
   public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityUuidMapperInterface $entity_uuid_mapper) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
+    $this->entityTypeManager = $entity_type_manager;
     $this->productStorage = $entity_type_manager->getStorage('commerce_product');
     $this->entityUuidMapper = $entity_uuid_mapper;
   }
@@ -77,4 +86,33 @@ class OrderItemProduct extends ConditionBase implements ContainerFactoryPluginIn
     return in_array($purchased_entity->getProductId(), $product_ids);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getPurchasableEntityIds() {
+    $variation_ids = [];
+
+    $product_ids = $this->getProductIds();
+    if (!empty($product_ids)) {
+      foreach ($this->productStorage->loadMultiple($product_ids) as $product) {
+        /** @var \Drupal\commerce_product\Entity\ProductInterface $product */
+        $variation_ids += $product->getVariationIds();
+      }
+    }
+
+    return array_values($variation_ids);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPurchasableEntities() {
+    if ($entity_ids = $this->getPurchasableEntityIds()) {
+      $storage = $this->entityTypeManager->getStorage('commerce_product_variation');
+      $entities = $storage->loadMultiple($entity_ids);
+    }
+
+    return $entities ?? [];
+  }
+
 }
diff --git a/modules/promotion/src/Plugin/Commerce/PromotionOffer/BuyXGetY.php b/modules/promotion/src/Plugin/Commerce/PromotionOffer/BuyXGetY.php
index 6781c5ee..d2d501b6 100644
--- a/modules/promotion/src/Plugin/Commerce/PromotionOffer/BuyXGetY.php
+++ b/modules/promotion/src/Plugin/Commerce/PromotionOffer/BuyXGetY.php
@@ -4,6 +4,8 @@ namespace Drupal\commerce_promotion\Plugin\Commerce\PromotionOffer;
 
 use Drupal\commerce\ConditionGroup;
 use Drupal\commerce\ConditionManagerInterface;
+use Drupal\commerce\Plugin\Commerce\Condition\PurchasableEntityConditionInterface;
+use Drupal\commerce\PurchasableEntityInterface;
 use Drupal\commerce_order\Adjustment;
 use Drupal\commerce_order\Entity\OrderItemInterface;
 use Drupal\commerce_order\PriceSplitterInterface;
@@ -14,6 +16,7 @@ use Drupal\commerce_promotion\Entity\PromotionInterface;
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -44,6 +47,13 @@ class BuyXGetY extends OrderPromotionOfferBase {
    */
   protected $conditionManager;
 
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
   /**
    * Constructs a new BuyXGetY object.
    *
@@ -59,11 +69,14 @@ class BuyXGetY extends OrderPromotionOfferBase {
    *   The splitter.
    * @param \Drupal\commerce\ConditionManagerInterface $condition_manager
    *   The condition manager.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, RounderInterface $rounder, PriceSplitterInterface $splitter, ConditionManagerInterface $condition_manager) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, RounderInterface $rounder, PriceSplitterInterface $splitter, ConditionManagerInterface $condition_manager, EntityTypeManagerInterface $entity_type_manager) {
     parent::__construct($configuration, $plugin_id, $plugin_definition, $rounder, $splitter);
 
     $this->conditionManager = $condition_manager;
+    $this->entityTypeManager = $entity_type_manager;
   }
 
   /**
@@ -255,6 +268,32 @@ class BuyXGetY extends OrderPromotionOfferBase {
     }
 
     $get_conditions = $this->buildConditionGroup($this->configuration['get_conditions']);
+    // @todo Make this behavior optional based on an additional promotion
+    //   setting.
+    if ($get_purchasable_entity = $this->findSinglePurchasableEntity($get_conditions)) {
+      $order_item = $this->findOrCreateOrderItem($get_purchasable_entity, $order_items);
+      $expected_get_quantity = $this->calculateExpectedGetQuantity($buy_quantities, $order_item);
+
+      // If the expected get quantity is non-zero, we need to either find an
+      // existing order item and update its quantity if needed, or create a new
+      // order item for $get_purchasable_entity.
+      if (Calculator::compare($expected_get_quantity, '0') !== 0) {
+        if (Calculator::compare($order_item->getQuantity(), $expected_get_quantity) === -1) {
+          $order_item->setQuantity($expected_get_quantity);
+        }
+
+        if ($order_item->isNew()) {
+          $order_item->set('order_id', $order->id());
+          $order_item->save();
+          $order->addItem($order_item);
+          $order_items = $order->getItems();
+        }
+        else {
+          $order_items[$order_item->id()] = $order_item;
+        }
+      }
+    }
+
     $get_order_items = $this->selectOrderItems($order_items, $get_conditions, 'ASC');
     $get_quantities = array_map(function (OrderItemInterface $order_item) {
       return $order_item->getQuantity();
@@ -368,6 +407,103 @@ class BuyXGetY extends OrderPromotionOfferBase {
     return $selected_order_items;
   }
 
+  /**
+   * Find the configured purchasable entity amongst the given conditions.
+   *
+   * @param \Drupal\commerce\ConditionGroup $conditions
+   *   The condition group.
+   *
+   * @return \Drupal\commerce\PurchasableEntityInterface|null
+   *   The purchasable entity, NULL if not found in the conditions.
+   */
+  protected function findSinglePurchasableEntity(ConditionGroup $conditions) {
+    foreach ($conditions->getConditions() as $condition) {
+      if ($condition instanceof PurchasableEntityConditionInterface) {
+        $purchasable_entity_ids = $condition->getPurchasableEntityIds();
+        if (count($purchasable_entity_ids) === 1) {
+          $purchasable_entities = $condition->getPurchasableEntities();
+          continue;
+        }
+      }
+    }
+
+    return !empty($purchasable_entities) ? reset($purchasable_entities) : NULL;
+  }
+
+  /**
+   * Attempt to find the given purchasable entity amongst the given order items.
+   *
+   * If the given purchasable entity isn't referenced by any order item, create
+   * an order item referencing it so we can automatically add it to the order.
+   *
+   * @param \Drupal\commerce\PurchasableEntityInterface $get_purchasable_entity
+   *   The "get" purchasable entity.
+   * @param \Drupal\commerce_order\Entity\OrderItemInterface[] $order_items
+   *   The order items.
+   *
+   * @return \Drupal\commerce_order\Entity\OrderItemInterface
+   *   An order item referencing the given purchasable entity.
+   */
+  protected function findOrCreateOrderItem(PurchasableEntityInterface $get_purchasable_entity, array $order_items) {
+    foreach ($order_items as $order_item) {
+      $purchased_entity = $order_item->getPurchasedEntity();
+      if ($purchased_entity->getEntityTypeId() == $get_purchasable_entity->getEntityTypeId()
+          && $purchased_entity->id() == $get_purchasable_entity->id()) {
+        return $order_item;
+      }
+    }
+
+    /** @var \Drupal\commerce_order\OrderItemStorageInterface $storage */
+    $storage = $this->entityTypeManager->getStorage('commerce_order_item');
+    $order_item = $storage->createFromPurchasableEntity($get_purchasable_entity, [
+      'quantity' => 0,
+    ]);
+    return $order_item;
+  }
+
+  /**
+   * Calculates the expected get quantity.
+   *
+   * @param array $buy_quantities
+   *   An array of buy quantities.
+   * @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item
+   *   The order item.
+   *
+   * @return string
+   *   The expected get quantity.
+   */
+  protected function calculateExpectedGetQuantity($buy_quantities, OrderItemInterface $order_item) {
+    $expected_get_quantity = '0';
+
+    // Ensure that any possible "get" quantity already in the order is always
+    // processed last.
+    if (!$order_item->isNew()) {
+      if (isset($buy_quantities[$order_item->id()])) {
+        $quantity = $buy_quantities[$order_item->id()];
+        unset($buy_quantities[$order_item->id()]);
+        $buy_quantities[$order_item->id()] = $quantity;
+      }
+    }
+
+    while (!empty($buy_quantities)) {
+      $this->sliceQuantities($buy_quantities, $this->configuration['buy_quantity']);
+      $expected_get_quantity = Calculator::add($expected_get_quantity, $this->configuration['get_quantity']);
+
+      // If the "get" purchasable entity is already in the order, we need to
+      // ensure that the already discounted quantity is not counted towards the
+      // buy quantities.
+      if (!$order_item->isNew()) {
+        $buy_quantities = $this->removeQuantities($buy_quantities, [$order_item->id() => $this->configuration['get_quantity']]);
+      }
+
+      if (array_sum($buy_quantities) < $this->configuration['buy_quantity']) {
+        break;
+      }
+    }
+
+    return $expected_get_quantity;
+  }
+
   /**
    * Takes a slice from the given quantity list.
    *
diff --git a/src/Plugin/Commerce/Condition/PurchasableEntityConditionInterface.php b/src/Plugin/Commerce/Condition/PurchasableEntityConditionInterface.php
new file mode 100644
index 00000000..0535d216
--- /dev/null
+++ b/src/Plugin/Commerce/Condition/PurchasableEntityConditionInterface.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Drupal\commerce\Plugin\Commerce\Condition;
+
+/**
+ * Defines the interface for conditions that deal with purchasable entities.
+ */
+interface PurchasableEntityConditionInterface {
+
+  /**
+   * @return int|string[]
+   *   An array of purchasable entity IDs.
+   */
+  public function getPurchasableEntityIds();
+
+  /**
+   * @return \Drupal\commerce\PurchasableEntityInterface[]
+   *   An array of purchasable entities.
+   */
+  public function getPurchasableEntities();
+
+}
