Group your Shipping Methods by Carrier on Checkout

Having several different shipping providers on your site improves the user experience, which is crucial for converting customers into buyers. This article will show you how to group the shipping methods by carrier provider and allow your users to choose their preferred shipping method.

If your site has several shipping vendors, it must be making quite some sales! One often overlooked aspect of the shipping process is how shipping options are presented to customers, especially if there are a large number of shipping methods. However, these can be reorganized to improve the UX for your customers. I’ll explain how.

Create Shipping Methods Mixin

To begin with, create requirejs-config.js in Magento_Checkout folder in your theme and paste the following code:

var config = {
    config: {
        mixins: {
            'Magento_Checkout/js/view/shipping': {
                'Magento_Checkout/js/view/shipping-mixin': true
            }
        }
    }
}

Group Shipping Methods by Barrier Code

In shipping-mixin.js file, paste the following code:

define([
], function () {
    'use strict';
 
    return function (Shipping) {
        return Shipping.extend({
            carriers: function () {
                const carrierCodes = this.rates.map(item => item.carrier_code).filter((value, index, self) => self.indexOf(value) === index)
                const carriers = carrierCodes.map(carrierCode => this.rates.filter(rate => rate.carrier_code === carrierCode));
 
                return carriers;
            }
        })
    }
})

What the snippet will do is as follows:

  1. variable carrierCodes will get ahold of unique carrier codes only
  2. variable carriers will group all shipping methods that belong to the same carrier

See output in DevTools to see the actual data:
console logging the variables

Modify Template Files

In order to complete the change, it is necessary to customize two files. Copy shipping-method-list.html and shipping-method-item.html files located in vendor/magenot/module-checkout/view/frontend/web/template/shipping-addres folder into your own theme.

In shipping-method-list.html the carriers() variable is used from the mixin.

<div id="checkout-shipping-method-load">
    <table class="table-checkout-shipping-method">
        <tbody>
        <!-- ko foreach: { data: carriers(), as: 'carrier'} -->
        <!--ko template: { name: element.shippingMethodItemTemplate} --><!-- /ko -->
        <!-- /ko -->
        </tbody>
    </table>
</div>

Next, paste the following snippet into shipping-method-item.html. If needed, this snippet should be adjusted per the design requirements of the project itself. The only requirement is to ensure the click event listener is working properly (which can be checked in the Billing step (step 2).

<!-- ko foreach: { data: carrier, as: 'method'} -->
<!-- ko if: ($index() === 0) -->
<tr class="shipping-carrier-title">
    <td colspan="2">
        <strong data-bind="text: method.carrier_title"></strong>
    </td>
</tr>
<!-- /ko  -->
 
<tr class="shipping-method-details" click="element.selectShippingMethod">
    <td class="shipping-method-title"
        attr="'id': 'label_method_' + method.method_code + '_' + method.carrier_code">
 
        <div class="choice">
            <input type="radio"
                   class="radio"
                   ifnot="method.error_message"
                   ko-checked="element.isSelected"
                   ko-value="method.carrier_code + '_' + method.method_code"
                   attr="'aria-labelledby': 'label_method_' + method.method_code + '_' + method.carrier_code + ' ' + 'label_carrier_' + method.method_code + '_' + method.carrier_code,
                    'checked': element.rates().length == 1 || element.isSelected,
                    'id': 'label_method_' + method.method_code + '_' + method.carrier_code + ' ' + 'label_carrier_' + method.method_code + '_' + method.carrier_code"
            />
            <label class="label"
                   data-bind="
                    text: method.method_title,
                    attr: {
                        for: 'label_method_' + method.method_code + '_' + method.carrier_code + ' ' + 'label_carrier_' + method.method_code + '_' + method.carrier_code
               }"
            ></label>
        </div>
 
        <div if="method.error_message" class="shipping-method-message">
            <div role="alert" class="message error">
                <div text="method.error_message"></div>
            </div>
            <span class="no-display">
                <input type="radio"
                       attr="'value' : method.method_code, 'id': 's_method_' + method.method_code">
            </span>
        </div>
    </td>
    </td>
    <!-- ko ifnot: (method.error_message) -->
    <td class="shipping-method-price">
        <each args="element.getRegion('price')" render=""></each>
    </td>
    <!-- /ko -->
</tr>
<!-- /ko -->

The last part is to make some minor styling adjustments to override default styling and make things look better. ๐Ÿ™‚

.table-checkout-shipping-method {
    width: 100%;
}
 
.shipping-method-title {
    width: auto !important;
}
 
.shipping-carrier-title td {
    padding-bottom: 0 !important;
    border-top: 0 !important;
}
 
.shipping-method-details td {
    border-top: none !important;
    padding-bottom: 0 !important;
}
 
.shipping-method-details .choice {
    display: inline-flex;
    align-items: center;
}

Grouped shipping methods

Implementation in a Custom Project

To demonstrate code changes in best possible way, let’s see this implementation of shipping methods with actual vendors and several shipping methods. Here is the actual display of how it looks like on a Blank theme using UPS, USPS, FedEx. Store Pickup is not the default one, but of Amasty (Amasty_StorePickup).

Group multiple shipping methods

Believe it or not, but that’s it! Nothing too complicated, isn’t it? ๐Ÿ™‚
If you have any questions, leave a comment below. Until next time! ๐Ÿ™‚