Selling Bundles the Right Way on Magento 2

Magento has a variety of product types to help you find the right way to sell your products. There are downloadable products for digital items (such as ebooks), virtual products for items with no tangible deliverable (such as warranties), and configurable products for variations (such as a shirt that comes in different sizes). One of the most powerful types available is bundle products. In my experience, this is the least used product type, and I believe there are several factors influencing its sparse appearance:

  • External Integrations – Whether you’re pulling product data into Magento or pushing order data out, few systems match up with Magento’s concept of bundles.
  • Labor-Intensive Product Creation – Bundle products just take more time to set up than most other product types.
  • No Need – Many merchants simply find that they don’t sell any products in a bundle format.

Now let’s take a look at the upsides of bundle products by breaking down what they are and how you can leverage them to sell more in your store.

What is a Bundle Product?

In a nutshell, bundle products are a way of selling several products together as a single package. This is really a great opportunity for merchants since it’s a form of upselling. More than that, it’s a very targeted form of upselling, and Magento gives you the flexibility to really make it worth the customer’s while.

The bundle product is just a shell, and ultimately it contains the products that you’re already selling on your Magento store. Let’s say you sell musical instruments. You’d love it if that customer would add some cymbals and drumsticks to their drum set purchase. Rather than just showing related products and upsells during their purchase experience, you can create a bundle. By doing this, you encourage the customer to increase their order total, and you also give them confidence that the products they’re buying are compatible with each other.

You can read about creating and configuring bundle products in the official user guide, but I’m going to cover the basics of how bundles are put together.

Bundle Options

The basic architecture of bundle products is their options. Options are the components that make up the bundle. Each option offers a selection of products that can fill that slot. Let’s look at our drum set bundle. It’s going to have the following options:

  • Shell Pack (the drums themselves)
  • Crash Cymbal
  • Hi Hat
  • Drum Sticks

At a basic level, those are the things included in the bundle. For each of those options, there can be multiple products to choose from depending on your preference. My product represents a bundle around a Yamaha Stage Custom shell pack, so I’m only going to offer one product for the Shell Pack option. However, the customer will be able to choose from multiple brands to fill the Crash Cymbal, Hi-Hat and Drum Sticks options.

You can make each option required or optional, set default quantities for selections, and choose whether or not the customer can define their own quantities.

For my product, most of the options are required and have an unchangeable quantity of 1. However, the drumsticks are an optional add-on, and the customer can modify the quantity. This ensures that someone new to drum purchasing doesn’t accidentally buy things they don’t need, but they can buy a few extra pairs of sticks from the same product page.

Bundle Pricing

Pricing for bundle products is flexible. All of the simple products that can end up in my bundle have their own prices in Magento, but do I want to discount them when bundled?

In this case, I do. Depending on the products selected, the sum of the items in my bundle could range from $1,204 to $1,229. To encourage customers to buy my bundle, I’m going to price it at $1,100. The Shell Pack, Crash Cymbal, and Hi-Hat selections will be included at no additional cost. For the sticks (which are optional), I’m going to price them at $5 per pair. This is still a good deal, as the simple products for the drumsticks would be $8 if added to the cart separately.

To set up this kind of pricing, I set Dynamic Price = No when creating the bundle. This allows me to set a base price for the bundle, then optionally attach prices to each option selection. This gives the products specific prices in the context of the bundle, but the pricing of the simple products has no impact on my bundle.

If I don’t want discounts for my bundle, I can set Dynamic Price = Yes. Instead of manually setting prices on my option selections, the bundle will automatically sum the simple product prices. The user won’t save any money buying the bundle, but they’ll still benefit from a more guided shopping experience.

Bundle Data Architecture

You may be thinking that bundles sound great in theory, but how do they work out with other eCommerce elements? How does shipping calculation work? What does the data look like when orders are exported to an external system?

There’s some flexibility around the data. From a shipping perspective, bundles offer a Dynamic Weight option. If set to Yes, the weight of the bundle product in the cart will be the sum of all simple product weights in the bundle. This ensures that the final weight will be accurate for the purpose of getting shipping rates. If set to No, you can assign a static weight to the bundle that will be unaffected by the customer’s selections.

There’s also an option for Dynamic SKU. When set to Yes, the final SKU of the item will be a combination of the bundle product SKU and those of all the simple products included.

The final order data contains quite a bit of information about the bundle. It contains the bundle itself as well as a row for each product that ended up in the bundle. Those rows contain the SKU and quantity of the simple products. This is nice if you have an external integration consuming order data for things like inventory tracking, as it allows you to examine the tangible products that were sold.

Challenges with Bundles

Bundles are a complex concept, and every merchant will leverage them a little differently. I completely respect the logic that the Magento core uses with bundles, but it’s definitely not a “one size fits all” scenario. I’d like to look at some of the specific challenges I’ve seen merchants run into, and the solutions we implemented to get the logic just right.

The “Primary Product” Dilemma

Bundles work really well out of the box to sell several related products together. However, they don’t work as well to represent a primary product with add-ons. Last year, I had a merchant who sells espresso machines. They often include items with these machines, such as a bag of espresso beans or an extended warranty. They needed these freebies to be clearly represented on the product and cart pages, and they also needed them to be individual items in the order data for the sake of inventory updates. The problem was that they really didn’t want to offer the “Customize and Buy” interface that Magento provides out of the box. There was really nothing for the customer to configure. All options were required and only offered one selection. They really just needed the customer to see what’s included and have an add to cart button.

We were able to work around this with a few layout changes and some strategic CSS adjustments. Within the bundle configuration, we hid the option labels and form elements, leaving only the names of the selections and the prices. We also moved the master quantity input and the add to cart button above the bundle configuration. The end result was a product page that’s nearly as basic as a simple product. Here’s the same treatment on our drum set bundle if it only had one selection for each option:

Note that this removes all configuration mechanisms from the page, so it’s really only a good fit for bundles with static selections.

Shopping Cart Price Rules

When using shopping cart price rules, it’s important to be aware of the native behavior with bundle product children. Any conditions in a price rule will be triggered by the items in the bundle once it’s added to the customer’s cart. If this isn’t your intention with your price rules, you could get a nasty surprise when orders start coming in.

Let’s take our drum set bundle as an example. I have some surplus stock of Vic Firth drumsticks, so I want to offer a 50% off sale. I create a shopping cart price rule that takes 50% off of products where Brand is Vic Firth. This works great when customers go to the drumsticks category and purchase a matching simple product, but it’s less ideal when they add the drum set bundle to the cart. When configured to include the optional Vic Firth drumsticks, the 50% discount is applied to the entire bundle. In an effort to move some drumsticks, we’re suddenly giving away a $1,100 drum set for $550.

Unfortunately, there’s no configuration to change this behavior, but you can alter it with a small code change if necessary. The model responsible for this particular rule validation is MagentoSalesRuleModelRuleConditionProductCombine, and the relevant method is validate (defined in the superclass).

If we create a plugin for MagentoSalesRuleModelRuleConditionProductCombine, we can make sure that no bundle children are valid for shopping cart price rules.

public function aroundValidate(MagentoSalesRuleModelRuleConditionProductCombine $subject, callable $proceed, MagentoFrameworkModelAbstractModel $model) {
    if ($model->getParentItem() && $model->getParentItem()->getProductType() == 'bundle') {
        return false;
    }
    return $proceed($model);
}

Layered Navigation

Similar to the dilemma of shopping cart price rules, bundle children can also trigger unintended layered navigation behavior. Let’s say we put our drum set bundle in a Drum Sets category. Since the set is built around a Yamaha shell pack, we’ve set the brand on the bundle product itself as Yamaha. In layered navigation, we want the set to show up when Yamaha is set as the brand filter. Since we don’t expect customers to choose a bundle based on which drum sticks are included, we’d prefer that the bundle doesn’t show up when results are filtered to match a Brand value of Vic Firth.

Here’s the native handling of the bundle. Notice how every brand that’s available in our drum set bundle is present in the layered navigation:

Fortunately, we can exclude bundle child attributes from being considered in layered navigation with some minor code changes. The bundle child indexing occurs in MagentoCatalogModelResourceModelProductIndexerEavAbstractEav in the _prepareRelationIndexSelect method. Here’s the original code:

$connection = $this->getConnection();
$idxTable = $this->getIdxTable();
$linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField();
$select = $connection->select()->from(
    ['l' => $this->getTable('catalog_product_relation')],
    []
)->joinLeft(
    ['e' => $this->getTable('catalog_product_entity')],
    'e.' . $linkField .' = l.parent_id',
    ['e.entity_id as parent_id']
)->join(
    ['cs' => $this->getTable('store')],
    '',
    []
)->join(
    ['i' => $idxTable],
    'l.child_id = i.entity_id AND cs.store_id = i.store_id',
    ['attribute_id', 'store_id', 'value']
)->group(
    ['parent_id', 'i.attribute_id', 'i.store_id', 'i.value']
);
if ($parentIds !== null) {
    $select->where('e.entity_id IN(?)', $parentIds);
}

/**
 * Add additional external limitation
 */
$this->_eventManager->dispatch(
    'prepare_catalog_product_index_select',
    [
        'select' => $select,
        'entity_field' => new Zend_Db_Expr('l.parent_id'),
        'website_field' => new Zend_Db_Expr('cs.website_id'),
        'store_field' => new Zend_Db_Expr('cs.store_id')
    ]
);

return $select;

The query uses the catalog_product_relation table to identify parent / child product relationships, then joins the attribute index table on by child id.
This allows it to query the attribute values of child products of relevant parents. It also uses a left join to include the parent product.
It groups by the parent id, and the result is a list of parents that have children matching the filter criteria.

If we replace the method code with the following, we can instead limit the parent product join to products that aren’t bundles.
This will exclude bundle children from the query logic, producing layered navigation with no consideration of bundle child product attributes.

$connection = $this->getConnection();
$idxTable = $this->getIdxTable();

$select = $connection->select()->from(
    ['l' => $this->getTable('catalog_product_relation')],
    'parent_id'
)->join(
    ['cs' => $this->getTable('store')],
    '',
    []
)->join(
    ['i' => $idxTable],
    'l.child_id = i.entity_id AND cs.store_id = i.store_id',
    ['attribute_id', 'store_id', 'value']
)->join(
    ['p' => 'catalog_product_entity'],
    'l.parent_id = p.entity_id AND p.type_id != 'bundle'',
    []
)->group(
    ['l.parent_id', 'i.attribute_id', 'i.store_id', 'i.value']
);
if ($parentIds !== null) {
    $select->where('l.parent_id IN(?)', $parentIds);
}

/**
 * Add additional external limitation
 */
$this->_eventManager->dispatch(
    'prepare_catalog_product_index_select',
    [
        'select' => $select,
        'entity_field' => new Zend_Db_Expr('l.parent_id'),
        'website_field' => new Zend_Db_Expr('cs.website_id'),
        'store_field' => new Zend_Db_Expr('cs.store_id')
    ]
);

return $select;

Note that this method is in the abstract, so you’ll have to implement a di preference for each subclass that implements this method that you want to behave differently. When I implemented these changes, I included the following:

  • MagentoCatalogModelResourceModelProductIndexerEavDecimal
  • MagentoCatalogModelResourceModelProductIndexerEavSource
  • MagentoCatalogModelResourceModelProductIndexerEavSource

Here’s the resulting behavior:

Make a Bundle and Make a Bundle

Whether you’re looking to help customers identify compatible products, or just looking to increase your order totals, I think there’s a use case for almost every merchant to use bundle products. Even if you don’t want to create a lot of them, they can be a great promotional tool. So what are you waiting for? Get started with those bundle products. If you happen to need some customizations to get them just right, give us a call.

Share it

Topics

Related Posts

Google and Yahoo Have New Requirements for Email Senders

What ROAS Really Means

Everything You Need to Know About Updating to Google Analytics 4

Contact Us