Select your platform

Installation Guide for Subscriptions for Shopify Checkout

Laurel
Laurel
  • Updated

Bold Subscriptions for Shopify Checkout requires Liquid code in order to run correctly on your store. This ensures the subscription options appear on your product pages.

We offer a few different options to help you complete this installation on your store:

  • An automatic installation
  • An expert install performed by our team
  • A step-by-step guide to install the code manually on your vintage theme.
  • A step-by-step guide to install the app widget block on your Online Store 2.0 theme.

Before installing the Liquid code, it is recommended to create a duplicate version of your Shopify theme to ensure you have a copy without our code installation.

 


 

Automatic install

If you don't feel comfortable maunally installing the required Liquid code to your theme files, you can use the automatic installer. This installs the required code directly into your Shopify theme with the click of a button.

To request an installation, please follow the steps below.

  1. From the Shopify admin, select Apps.
  2. Select Bold Subscriptions.
  3. Within the apps menu, select Help.
  4. Select Theme code update.
  5. Select your preferred theme.
  6. Select I have created a backup of this theme.

    Note: Creating a backup of your theme is the same as creating a duplicate. Please visit Duplicate version of your Shopify theme for more information.

  7. Select Start automatic install.

Completing these steps will trigger our automatic installer to run on your store.

If your current theme is not compatible with our automatic installation tool, the app will automatically notify you. If this happens, you can rollback your theme to its previous state.

When the installation has not been completed in full, reverting the changes will automatically create an installation request for our team to assist you further.

 


 

Request an expert install

To maintain the security of your Shopify account, we ask that you do not proactively send us a staff account invite for access to your shop. Instead, our team will request collaborator access to your shop.

This option creates a ticket with our expert installation team to schedule your store for a manual installation. Our team will create a backup copy of your requested theme and complete the install manually on a rotational basis as soon as possible.

To request an expert install, please follow the steps below.

  1. From within Bold Subscriptions, navigate to Help > Theme code update.
  2. Select the text which says Expert theme update service.

    Expert theme update link

  3. On the bottom left corner, select the theme where you would like the installation completed.

    Theme selector

  4. Select Submit Request.

 


 

Manual install (vintage themes)

Note: If you are unsure of whether you are using a vintage or Online Store 2.0 theme, please visit Shopify’s article Theme architecture versions.

These instructions show you how to complete the Liquid code installation yourself. This method of installation is only suggested if you have a strong knowledge of Liquid coding.

Before you start the installation, please create a duplicate of your theme. This allows you to test your installation before making the theme live on your store.

It is recommended to complete an automatic installation first. The manual installation steps below can then be followed to ensure that everything is added correctly and displaying properly on your store.

To manually install the code to your files, please follow the steps below.

Step 1: Add the liquid files

If you have previously installed Subscriptions for Shopify Checkout on your store prior to March 2023, then you will have the bsub.scss file on your store, instead of the bsub.css file as shown in step 7 below. If you need to reinstall Bold Subscriptions, please use the new bsub.css file as shown below. Please note, any customizations you have made to the previous bsub.scss file will need to be recreated in the new file.

  1. From the Shopify admin, navigate to Sales channels Online Store.
  2. Duplicate the theme you would like the installation completed on.
  3. Select Edit code on the duplicated theme.
  4. Under Snippets, find the following files:
    • bsub-widget.liquid
    • bsub-cart.liquid

      File the snippet files

  5. If the snippet files do not currently exist, select Add a new snippet. If these files do exist, please skip to Step #6.
    1. Enter the correct snippet name.
    2. Select Create snippet.

      bsub-widget

    3. Copy and paste the the following code into their respective files:

      • {%- liquid
        if product.requires_selling_plan or product.selected_selling_plan_allocation
        assign current_selling_plan_allocation = product.selected_or_first_available_selling_plan_allocation
        else
        assign current_selling_plan_allocation = nil
        endif
        -%}

        <!-- Bold Subscriptions Widget -->
        {% if product.selling_plan_groups.size > 0 %}
        <fieldset
        class="bsub-widget"
        role="{%- if product.requires_selling_plan == false or product.selling_plan_groups.size > 1 -%} radiogroup {%- else -%} group {%- endif -%}"
        data-bsub-widget
        >
        <legend>
        {%- if product.requires_selling_plan and product.selling_plan_groups.size == 1 -%}
        {{ product.selling_plan_groups.first.name }}
        {%- else -%}
        Purchase Options
        <!-- {{ 'products.product.purchase_options' | t }} -->
        {%- endif -%}
        </legend>

        <div
        class="bsub-widget__wrapper
        {% if product.requires_selling_plan and product.selling_plan_groups.size == 1 %} bsub-widget__wrapper--single {% endif %}"
        >
        <!-- Selling Plan Groups (Purchase Options) -->
        <div class="bsub-widget__groups-container">
        <!-- render One-time purchase option -->
        {% unless product.requires_selling_plan == true %}
        <div class="bsub-widget__group">
        <label class="bsub-widget__group-label">
        <input
        type="radio" value="once" name="bsub-selling-plan-group"
        data-bsub-selling-plan-group-input
        data-bsub-purchase-option-one-time
        {% unless current_selling_plan_allocation %} checked {% endunless %}
        onchange="window.BOLD.BsubWidget.handleSellingPlanGroupChange(event)"
        >
        <div class="bsub-widget__group-header">
        <svg class="bsub-widget__image" viewBox="0 0 72 72" fill="currentColor">
        <g opacity="0.8">
        <path d="M30 6L13.5 13.5L18 16.5L31.5 22.5L36 33L40.5 22.5L54 16.5L58.5 13.5L42 6L36 18L30 6Z" fill="black" fill-opacity="0.2"/>
        <path fill-rule="evenodd" clip-rule="evenodd" d="M4.38849 10.3276L30.7645 20.2186L36 33.3073L41.2355 20.2186L67.6116 10.3276L61 30.1622V55.6139C61 56.8645 60.2243 57.9838 59.0534 58.4229L36 67.0679L12.9467 58.4229C11.7757 57.9838 11 56.8645 11 55.6139V30.1622L4.38849 10.3276ZM7.61156 13.6723L13 29.8376V55.6139C13 56.0308 13.2586 56.4039 13.6489 56.5503L36 64.9319L58.3512 56.5503C58.7415 56.4039 59 56.0308 59 55.6139V29.8376L64.3885 13.6722L42.7645 21.7812L36 38.6925L29.2355 21.7812L7.61156 13.6723Z" fill="currentColor"/>
        <path fill-rule="evenodd" clip-rule="evenodd" d="M36.12 19.9325L41.6899 22.0132L40.99 23.8868L36.12 22.0675L31.01 23.9768L30.31 22.1033L36.12 19.9325Z" fill="currentColor"/>
        <path fill-rule="evenodd" clip-rule="evenodd" d="M41.4523 4.67666L61.0545 13.0911L60.2656 14.9289L42.5477 7.32335L36.9285 21.3714L35.0715 20.6286L41.4523 4.67666Z" fill="currentColor"/>
        <path fill-rule="evenodd" clip-rule="evenodd" d="M30.5477 4.67666L36.9285 20.6286L35.0715 21.3714L29.4523 7.32335L11.7345 14.9289L10.9456 13.0911L30.5477 4.67666Z" fill="currentColor"/>
        <path fill-rule="evenodd" clip-rule="evenodd" d="M35.8245 39.4682L11.8245 30.4682L12.1756 29.5318L36.1756 38.5318L35.8245 39.4682Z" fill="currentColor"/>
        <path fill-rule="evenodd" clip-rule="evenodd" d="M36.1755 39.4682L60.1755 30.4682L59.8244 29.5318L35.8244 38.5318L36.1755 39.4682Z" fill="currentColor"/>
        <path fill-rule="evenodd" clip-rule="evenodd" d="M36.5 39V66H35.5V39H36.5Z" fill="currentColor"/>
        </g>
        </svg>
        <div class="bsub-widget__text">
        One-time Purchase
        <!-- {{ 'products.product.one_time_purchase' | t }} -->
        </div>
        </div>
        </label>
        </div>
        {% endunless %}

        <!-- selling plan group radio -->
        {% for group in product.selling_plan_groups %}
        <div
        class="bsub-widget__group"
        data-bsub-selling-plan-group
        data-bsub-selling-plan-group-id="{{ group.id }}"
        >
        <label class="bsub-widget__group-label">
        <input
        data-bsub-selling-plan-group-input
        class="bsub-widget__input"
        type="radio" value="{{group.id}}" name="bsub-selling-plan-group"
        {% if group.id == current_selling_plan_allocation.selling_plan.group_id %} checked {% endif %}
        onchange="window.BOLD.BsubWidget.handleSellingPlanGroupChange(event)"
        >
        <div class="bsub-widget__group-header">
        <svg class="bsub-widget__image" viewBox="0 0 72 72" fill="currentColor">
        <g opacity="0.8">
        <path fill-rule="evenodd" clip-rule="evenodd" d="M11 16C10.4477 16 10 16.4477 10 17V32.01H8V17C8 15.3431 9.34315 14 11 14H40C41.6569 14 43 15.3431 43 17V49H40.95V47H41V17C41 16.4477 40.5523 16 40 16H11Z" fill="currentColor"/>
        <path d="M51 54C54.3137 54 57 51.3137 57 48C57 44.6863 54.3137 42 51 42C47.6863 42 45 44.6863 45 48C45 51.3137 47.6863 54 51 54Z" fill="currentColor" fill-opacity="0.2"/>
        <path fill-rule="evenodd" clip-rule="evenodd" d="M51 43C48.2386 43 46 45.2386 46 48C46 50.7614 48.2386 53 51 53C53.7614 53 56 50.7614 56 48C56 45.2386 53.7614 43 51 43ZM44 48C44 44.134 47.134 41 51 41C54.866 41 58 44.134 58 48C58 51.866 54.866 55 51 55C47.134 55 44 51.866 44 48Z" fill="currentColor"/>
        <path fill-rule="evenodd" clip-rule="evenodd" d="M53.5858 22H42V20H54.4142L67 32.5858V49H57V47H65V33.4142L53.5858 22Z" fill="currentColor"/>
        <path fill-rule="evenodd" clip-rule="evenodd" d="M50.5 28.7929L55.2071 33.5H50.5V28.7929ZM51.5 31.2071V32.5H52.7929L51.5 31.2071Z" fill="currentColor"/>
        <path fill-rule="evenodd" clip-rule="evenodd" d="M42 47H45V49H42V47Z" fill="currentColor"/>
        <path fill-rule="evenodd" clip-rule="evenodd" d="M24 24.94C14.6112 24.94 7 32.5512 7 41.94C7 51.3288 14.6112 58.94 24 58.94C33.3888 58.94 41 51.3288 41 41.94C41 32.5512 33.3888 24.94 24 24.94ZM5 41.94C5 31.4466 13.5066 22.94 24 22.94C34.4934 22.94 43 31.4466 43 41.94C43 52.4334 34.4934 60.94 24 60.94C13.5066 60.94 5 52.4334 5 41.94Z" fill="currentColor"/>
        <path fill-rule="evenodd" clip-rule="evenodd" d="M23.5 32.94H24.5V41.7329L30.3536 47.5864L29.6464 48.2936L23.5 42.1471V32.94Z" fill="currentColor"/>
        </g>
        </svg>
        <div class="bsub-widget__text">
        <span>{{- group.name -}}</span>
        <br/>
        <span
        class="bsub-widget__group-discount-summary"
        data-bsub-group-discount-summary
        ></span>
        </div>
        </div>
        </label>
        </div>
        {% endfor %}
        </div>

        <!-- Render individual selling plans -->
        {% for group in product.selling_plan_groups %}
        <div
        class="bsub-widget__plans-container
        {% unless current_selling_plan_allocation.selling_plan.group_id == group.id %} bsub__hidden {% endunless %}"
        data-bsub-selling-plan-group-id="{{ group.id }}"
        data-bsub-selling-plans-container
        >
        <fieldset>
        <legend>
        Delivery Frequency
        </legend>
        {% for plan in group.selling_plans %}
        <div
        class="bsub-widget__plan"
        data-bsub-selling-plan
        data-bsub-selling-plan-id="{{ plan.id }}"
        >
        <label class="bsub-widget__plan-label">
        <input
        data-bsub-selling-plan-input
        type="radio" value="{{ plan.id }}"
        name="bsub-selling-plan-{{ group.id }}"
        {% if current_selling_plan_allocation == nil and forloop.first %} checked {% endif %}
        {% if current_selling_plan_allocation.selling_plan.id == plan.id -%} checked {% endif %}
        onchange="window.BOLD.BsubWidget.handleSellingPlanChange(event)"
        >
        <div class="bsub-widget__plan-header">
        <svg class="bsub-widget__checked-icon bsub-widget__image" viewBox="0 0 24 24">
        <path fill="currentColor" d="M24,12 C24,18.627 18.627,24 12,24 C5.373,24 0,18.627 0,12 C0,5.373 5.373,0 12,0 C18.627,0 24,5.373 24,12 Z M7.0050175,11.4087067 C6.61372743,11.0189496 5.98056367,11.0201924 5.59080666,11.4114825 C5.20104965,11.8027726 5.20229244,12.4359363 5.5935825,12.8256933 L9.9325825,17.1476933 C10.3226506,17.5362331 10.9534086,17.5363886 11.3436681,17.1480412 L19.5076681,9.02404115 C19.8991503,8.63447708 19.9007052,8.00131401 19.5111412,7.60983186 C19.1215771,7.2183497 18.488414,7.21679478 18.0969319,7.60635885 L10.6386478,15.0281006 L7.0050175,11.4087067 Z"></path>
        </svg>
        <svg class="bsub-widget__unchecked-icon bsub-widget__image" viewBox="0 0 18 18" fill="none">
        <circle cx="9" cy="9" r="9" fill="white" fill-opacity="0.1"/>
        <circle cx="9" cy="9" r="8.5" stroke="black" stroke-opacity="0.2"/>
        </svg>
        <div class="bsub-widget__text">
        {{- plan.name -}}
        </div>
        <div class="bsub-widget__plan-pricing">
        <span data-bsub-per-delivery-price></span>
        <span>&nbsp;/&nbsp;</span>
        <span data-bsub-delivery-frequency>delivery</span>
        </div>
        </div>
        </label>
        </div>
        {% endfor %}
        </fieldset>
        </div>
        {% endfor %}
        </div>

        <input
        type="hidden"
        name="selling_plan"
        data-bsub-selling-plan-id-input
        value="{{ current_selling_plan_allocation.selling_plan.id }}"
        />

        <script
        type="application/json"
        data-bsub-product-json
        data-bsub-product-id="{{ product.id }}"
        >
        {{ product | json }}
        </script>
        </fieldset>

        <script
        type="application/json"
        data-bsub-money-format="{{shop.money_format}}"
        ></script>

        <input
        type="hidden"
        data-bsub-page-template
        value="{{ template }}"
        />

        {% endif %}

      • {% if item.selling_plan_allocation != empty %}
        <span class="selling-plan-details" data-bsub-item-key="{{item.key}}">
        {{item.selling_plan_allocation.selling_plan.name}}
        </span>
        {% endif %}
    4. Select Save
  6. Under Assets, find the following files:
    • bsub.js
    • bsub.css

      Asset files

  7. If the Asset files do not currently exist, select Add a new asset. If they do exist, please skip to Step 2: Edit product.liquid.
    1. Select Create a blank file.
    2. Enter the correct asset name.
    3. Select either .scss or .js for the file type.
    4. Select Add asset.

      Blank asset

    5. Copy and paste the follwing code into their respective files:
      • var BsubWidget = (function () {
            function BsubWidget() {
                this.attrs = {
                    purchaseOptionOneTime: 'data-bsub-purchase-option-one-time',
                    sellingPlanGroupId: 'data-bsub-selling-plan-group-id',
        
                    sellingPlanIdInput: 'data-bsub-selling-plan-id-input',
        
                    widget: 'data-bsub-widget',
                    sellingPlanOptionsContainer: 'data-bsub-selling-plan-options-container',
                    sellingPlanOption: 'data-bsub-selling-plan-option',
                    sellingPlansContainer: 'data-bsub-selling-plans-container',
                    sellingPlanGroup: 'data-bsub-selling-plan-group',
                    sellingPlanGroupInput: 'data-bsub-selling-plan-group-input',
                    sellingPlan: 'data-bsub-selling-plan',
                    sellingPlanInput: 'data-bsub-selling-plan-input',
                    productJson: 'data-bsub-product-json',
                    groupDiscountSummary: 'data-bsub-group-discount-summary',
                    perDeliveryPrice: 'data-bsub-per-delivery-price',
                    cartPopupDetails: 'data-bsub-cart-popup-details',
                    cartPageDetails: 'data-bsub-cart-page-details',
                    moneyFormat: 'data-bsub-money-format',
                    pageTemplate: 'data-bsub-page-template',
                }
        
                this.selectors = {
                    productForm: 'form[action="/cart/add"]',
                    variantIdInput: '[name="id"]',
                    variantSelector: ['#shappify-variant-id', '.single-option-selector', 'select[name=id]', 'input[name=id]'],
                };
        
                // autogenerate selectors from attributes
                Object.entries(this.attrs).forEach(function ([key, value]) {
                    this.selectors[key] = `[${value}]`;
                }.bind(this));
        
                this.classes = {
                    hidden: 'bsub__hidden',
                };
        
                this.products = {};
                this.variants = {};
                this.sellingPlanGroups = {};
                this.pageTemplate = '';
            }
        
            BsubWidget.prototype = Object.assign({}, BsubWidget.prototype, {
                init: function () {
                    console.debug('BSUB: Initializing widgets...');
                    if (!document.querySelector(this.selectors.widget)) {
                        console.debug('BSUB: No widgets detected, skipping initialization.');
                        return;
                    }
                    this._parsePageTemplate();
                    this._parseProductJson();
                    this._addVariantChangeListener();
        
                    var widgets = document.querySelectorAll(this.selectors.widget);
                    widgets.forEach(function (widget) {
                        this._renderPrices(widget);
                        this._renderGroupDiscountSummary(widget);
                    }.bind(this));
        
                    window.addEventListener("pageshow", function () {
                        this.syncAllVisuallySelected();
                    }.bind(this));
        
                    console.debug('BSUB: Successfully initialized widgets.');
                },
        
                /**
                 * Set the hidden selling_plan input to the visually selected plan in the widget.
                 * The browser caches form state between back and forward navigations, but doesn't emit change events.
                */
                syncAllVisuallySelected: function () {
                    var widgets = document.querySelectorAll(this.selectors.widget);
                    widgets.forEach(this._syncVisuallySelected.bind(this));
                },
        
                _syncVisuallySelected: function (widget) {
                    var selectedGroupEl = widget.querySelector(`${this.selectors.sellingPlanGroupInput}:checked`);
                    selectedGroupEl.dispatchEvent(new Event('change'));
                },
        
                _addVariantChangeListener: function () {
                    var selectors = document.querySelectorAll(this.selectors.variantSelector.join())
                    selectors.forEach(function (select) {
                        if (select) {
                            select.addEventListener('change', function (event) {
                                var productForm = event.target.closest(this.selectors.productForm);
                                var widget = productForm.querySelector(this.selectors.widget);
        
                                // NOTE: Variant change event needs to propagate to `input[name=id]`, so wait for that to happen...
                                setTimeout(function () {
                                    this._renderPrices(widget);
                                    this._renderGroupDiscountSummary(widget);
                                }.bind(this), 100)
                            }.bind(this));
                        }
                    }.bind(this));
                },
        
                _parsePageTemplate: function () {
                    var pageTemplateInputEl = document.querySelector(this.selectors.pageTemplate);
                    if (pageTemplateInputEl === null) {
                        return;
                    }
                    this.pageTemplate = pageTemplateInputEl.value;
                },
        
                _parseProductJson: function () {
                    var productJsonElements = document.querySelectorAll(this.selectors.productJson);
        
                    productJsonElements.forEach(function (element) {
                        var productJson = JSON.parse(element.innerHTML);
                        this.products[element.dataset.bsubProductId] = productJson;
        
                        productJson.selling_plan_groups.forEach(function (sellingPlanGroup) {
                            this.sellingPlanGroups[sellingPlanGroup.id] = sellingPlanGroup;
                        }.bind(this));
        
                        productJson.variants.forEach(function (variant) {
                            this.variants[variant.id] = variant;
                        }.bind(this));
                    }.bind(this));
                },
        
                renderAllPrices: function () {
                    var widgets = document.querySelectorAll(this.selectors.widget);
                    widgets.forEach(this._renderPrices.bind(this));
                },
        
                /**
                 * Display "price / delivery" for each plan label.
                 * Should run again if variant changes.
                 */
                _renderPrices: function (widget) {
                    if (typeof widget === 'undefined'){
                        widget = document.querySelector(this.selectors.widget);
                    }
        
                    var planRadioEls = widget.querySelectorAll(
                        this.selectors.sellingPlan,
                    );
        
                    var variantId = this._getVariantId(widget);
        
                    if (variantId){
                        planRadioEls.forEach(function (element) {
                            var sellingPlanId = element.dataset.bsubSellingPlanId;
                            var sellingPlanAllocation = this._getSellingPlanAllocation(variantId, sellingPlanId);
                            var priceEl = element.querySelector(this.selectors.perDeliveryPrice);
        
                            var price = sellingPlanAllocation.per_delivery_price;
        
                            var formattedPrice = this._formatPrice(price);
        
                            priceEl.innerHTML = formattedPrice;
                        }.bind(this));
                    }
                },
        
                renderAllGroupDiscountSummary: function () {
                    var widgets = document.querySelectorAll(this.selectors.widget);
                    widgets.forEach(this._renderGroupDiscountSummary.bind(this));
                },
        
                _renderGroupDiscountSummary: function (widget) {
                    var productJsonEl = widget.querySelector(this.selectors.productJson);
                    var productId = productJsonEl.dataset.bsubProductId;
        
                    var groupRadioEls = widget.querySelectorAll(
                        this.selectors.sellingPlanGroup,
                    );
        
                    groupRadioEls.forEach(function (element) {
                        var groupId = element.dataset.bsubSellingPlanGroupId;
                        var product = this.products[productId];
                        var sellingPlanGroup = product.selling_plan_groups.find(function (group) {
                            return group.id === groupId;
                        });
        
                        var discounts = sellingPlanGroup.selling_plans.map(function (plan) {
                            if (plan.price_adjustments.length === 0) {
                                return { value: 0, type: '' };
                            }
        
                            return {
                                value: plan.price_adjustments[0].value,
                                type: plan.price_adjustments[0].value_type,
                            };
                        });
        
                        var maxDiscount = discounts.reduce(function (a, b) {
                            return a.value  b.value ? a : b;
                        });
        
                        if (maxDiscount.value === 0) {
                            return;
                        }
        
                        var upTo = discounts.some(function (discount) {
                            return discount.value !== maxDiscount.value;
                        })
        
                        var summaryEl = element.querySelector(this.selectors.groupDiscountSummary);
        
                        var summaryString = '(Save'
                        if (upTo) {
                            summaryString += ' up to ';
                        }
        
                        switch (maxDiscount.type) {
                            case 'fixed_amount':
                                summaryString += this._formatPrice(maxDiscount.value);
                                break;
                            case 'percentage':
                            default:
                                summaryString += ` ${maxDiscount.value}%`;
                        }
                        summaryString += ')'
        
                        summaryEl.innerHTML = summaryString;
                    }.bind(this));
                },
        
                handleSellingPlanGroupChange: function (event) {
                    var groupRadioEl = event.target;
                    var groupId = groupRadioEl.value;
                    var widget = groupRadioEl.closest(this.selectors.widget)
        
                    var plansContainers = widget.querySelectorAll(this.selectors.sellingPlansContainer);
        
                    plansContainers.forEach(function (plansContainer) {
                        var plansContainerGroupId = plansContainer.dataset.bsubSellingPlanGroupId;
        
                        if (plansContainerGroupId === groupId && groupRadioEl.checked) {
                            plansContainer.classList.remove(this.classes.hidden);
                        } else {
                            plansContainer.classList.add(this.classes.hidden);
                        }
                    }.bind(this));
        
                    if (groupId === 'once') {
                        this._setSellingPlanIdInput(widget, "");
                        return;
                    }
        
                    // TODO: Implement setting for plan options vs. plan list
                    // var selectedOptions = this._getSellingPlanOptions(groupId);
                    // var sellingPlan = this._getSellingPlanFromOptions(groupId, selectedOptions);
        
                    var sellingPlanId = this._getActiveSellingPlanId(widget, groupId);
                    this._setSellingPlanIdInput(widget, sellingPlanId);
                },
        
                handleSellingPlanChange: function (event) {
                    var planRadioEl = event.target;
                    var widget = planRadioEl.closest(this.selectors.widget);
                    this._setSellingPlanIdInput(widget, planRadioEl.value);
                },
        
                // NOTE: Selling Plan Options not supported in the current version of the widget...
                handleSellingPlanOptionChange: function (event) {
                    var widget = event.target.closest(this.selectors.widget);
        
                    var sellingPlanGroupId = event.target.dataset.bsubSellingPlanGroupId;
                    var selectedOptions = this._getSellingPlanOptions(widget, sellingPlanGroupId);
                    var sellingPlan = this._getSellingPlanFromOptions(sellingPlanGroupId, selectedOptions);
                    this._setSellingPlanIdInput(sellingPlan.id);
                },
        
                _setSellingPlanIdInput: function (widget, sellingPlanId) {
                    var sellingPlanIdInput = widget.querySelector(this.selectors.sellingPlanIdInput);
                    var variantId = this._getVariantId(widget);
        
                    sellingPlanIdInput.value = sellingPlanId;
                    if (/.*(product).*/.test(this.pageTemplate)) {
                        this._updateHistoryState(variantId, sellingPlanId);
                    }
                },
        
                _getSellingPlanGroup: function (groupId) {
                    if (!this.sellingPlanGroups[groupId]) {
                        console.error('BSUB: Selling plan group data not found.');
                        return;
                    }
        
                    return this.sellingPlanGroups[groupId];
                },
        
                // NOTE: Selling Plan Options not supported in the current version of the widget...
                _getSellingPlanOptions: function (widget, sellingPlanGroupId) {
                    var sellingPlanOptions = widget.querySelectorAll(
                        `${this.selectors.sellingPlanOption}[${this.attrs.sellingPlanGroupId}="${sellingPlanGroupId}"]:checked`
                    );
        
                    var selectedOptions = [];
                    sellingPlanOptions.forEach(function (optionElement) {
                        selectedOptions.push({
                            index: optionElement.dataset.bsubOptionIndex,
                            value: optionElement.value,
                        });
                    });
        
                    return selectedOptions;
                },
        
                // NOTE: Selling Plan Options not supported in the current version of the widget...
                _getSellingPlanFromOptions: function (groupId, selectedOptions) {
                    var sellingPlans = (this._getSellingPlanGroup(groupId)).selling_plans;
        
                    var planFromOptions = sellingPlans.find(function (plan) {
                        return selectedOptions.every(function (option) {
                            return plan.options[option.index].value === option.value;
                        });
                    });
        
                    return planFromOptions;
                },
        
                _getVariantId: function (widget) {
                    var productForm = widget.closest(this.selectors.productForm);
                    if (!productForm) {
                        console.error('BSUB: Could not find product form.');
                        return null;
                    }
                    var variantIdInput = productForm.querySelector(this.selectors.variantIdInput);
        
                    return variantIdInput.value;
                },
        
                _getActiveSellingPlanId: function (widget, groupId) {
                    var activePlanInputEl = widget.querySelector(
                        `input[name=bsub-selling-plan-${groupId}]:checked`,
                    );
        
                    if (!activePlanInputEl) {
                        console.error(`BSUB: Could not find active plan ID for group ${groupId}.`);
                    }
        
                    return activePlanInputEl.value;
                },
        
                _updateHistoryState: function (variantId, sellingPlanId) {
                    if (!history.replaceState || !variantId) {
                        return;
                    }
        
                    var newurl =
                        window.location.protocol +
                        '//' +
                        window.location.host +
                        window.location.pathname +
                        '?';
        
                    if (sellingPlanId) {
                        newurl += 'selling_plan=' + sellingPlanId + '&';
                    }
        
                    newurl += 'variant=' + variantId;
        
                    window.history.replaceState({ path: newurl }, '', newurl);
                },
        
                /**
                 * SellingPlanAllocation is the the information of how a selling plan applies to a
                 * specific variant.
                 */
                _getSellingPlanAllocation(variantId, sellingPlanId) {
                    var variant = this.variants[variantId];
        
                    if (!variant) {
                        console.error(`BSUB: Could not find variant ID ${variantId}`);
                        return null;
                    }
        
                    return variant.selling_plan_allocations.find(function (plan) {
                        return `${plan.selling_plan_id}` === sellingPlanId;
                    });
                },
        
                _formatPrice(cents, format) {
                    var moneyElement = document.querySelector(this.selectors.moneyFormat)
                    var moneyFormat = moneyElement ? moneyElement.getAttribute('data-bsub-money-format') : null
        
                    if (typeof cents === 'string') {
                        cents = cents.replace('.', '');
                    }
        
                    var value = '';
                    var placeholderRegex = /\{\{\s*(\w+)\s*\}\}/;
                    var formatString = format || moneyFormat || theme.moneyFormat || theme.strings.moneyFormat || Shopify.money_format || "$ {% raw %}{{ amount }}{% endraw %}";
        
                    function formatWithDelimiters(number, precision, thousands, decimal) {
                        thousands = thousands || ',';
                        decimal = decimal || '.';
        
                        if (isNaN(number) || number === null) {
                            return 0;
                        }
        
                        number = (number / 100.0).toFixed(precision);
        
                        var parts = number.split('.');
                        var dollarsAmount = parts[0].replace(
                            /(\d)(?=(\d\d\d)+(?!\d))/g,
                            '$1' + thousands
                        );
                        var centsAmount = parts[1] ? decimal + parts[1] : '';
        
                        return dollarsAmount + centsAmount;
                    }
        
                    switch (formatString.match(placeholderRegex)[1]) {
                        case 'amount':
                            value = formatWithDelimiters(cents, 2);
                            break;
                        case 'amount_no_decimals':
                            value = formatWithDelimiters(cents, 0);
                            break;
                        case 'amount_with_comma_separator':
                            value = formatWithDelimiters(cents, 2, '.', ',');
                            break;
                        case 'amount_no_decimals_with_comma_separator':
                            value = formatWithDelimiters(cents, 0, '.', ',');
                            break;
                        case 'amount_no_decimals_with_space_separator':
                            value = formatWithDelimiters(cents, 0, ' ');
                            break;
                        case 'amount_with_apostrophe_separator':
                            value = formatWithDelimiters(cents, 2, "'");
                            break;
                    }
        
                    return formatString.replace(placeholderRegex, value);
                }
            })
        
            return BsubWidget;
        })();
        
        document.addEventListener('DOMContentLoaded', function () {
            window.BOLD = window.BOLD || {};
            window.BOLD.BsubWidget = new BsubWidget();
            window.BOLD.BsubWidget.init();
        });
        
      • @keyframes bsub-fadeInFromNone {
        	 0% {
        		 display: none;
        		 opacity: 0;
        	}
        	 1% {
        		 display: block;
        		 opacity: 0;
        	}
        	 100% {
        		 display: block;
        		 opacity: 1;
        	}
        }
         .bsub__hidden {
        	 display: none;
        }
         .bsub-widget {
        	 padding: 0 5px !important;
        	 border: 0 !important;
        	 margin: 0 !important;
        }
         .bsub-widget legend {
        	 margin-bottom: 5px;
        }
         .bsub-widget__wrapper {
        	 padding: 24px;
        	 border-radius: 8px;
        	 border: 1px solid rgba(0, 0, 0, 0.4);
        	 background-color: #f8f9f9;
        	 font-size: 14px;
        }
         .bsub-widget__wrapper fieldset {
        	 border: 0;
        	 background-color: inherit;
        	 margin: 0;
        	 padding: 0;
        }
         .bsub-widget__wrapper legend {
        	 font-size: 11px;
        	 text-transform: uppercase;
        	 letter-spacing: 3px;
        }
         .bsub-widget__wrapper--single .bsub-widget__groups-container {
        	 display: none;
        }
         .bsub-widget__wrapper--single .bsub-widget__plans-container, .bsub-widget__wrapper--single .bsub-widget__options-container {
        	 margin-top: 0;
        }
         .bsub-widget__description {
        	 margin-top: 20px;
        	 padding-top: 10px;
        	 color: black;
        	 border-top: 1px solid rgba(0, 0, 0, 0.1);
        }
         .bsub-widget__groups-container {
        	 display: flex;
        	 align-items: stretch;
        }
         .bsub-widget__groups-container input[type='radio'] {
        	 display: none;
        }
         .bsub-widget__groups-container:only-child {
        	 margin-bottom: 0;
        }
         .bsub-widget__group {
        	 flex: 1 1 100%;
        }
         .bsub-widget__group + .bsub-widget__group {
        	 margin-left: 1em;
        }
         .bsub-widget__group-header {
        	 display: flex !important;
        	 flex-direction: column;
        	 align-items: center;
        	 justify-content: center;
        	 height: 100%;
        	 text-align: center;
        	 padding: 1rem;
        	 transition: 0.3s;
        	 border: 1px solid rgba(0, 0, 0, 0.1);
        	 border-radius: 5px;
        	 background-color: white;
        }
         .bsub-widget__group-header .bsub-widget__image {
        	 display: block;
        	 width: 4em;
        	 height: 4em;
        }
         .bsub-widget__group-header:hover {
        	 box-shadow: 0px 0px 2px 2px rgba(0, 0, 0, 0.2);
        }
         input:checked + .bsub-widget__group-header {
        	 border-color: #3b63ff;
        	 color: #3b63ff;
        	 font-weight: 700;
        }
         .bsub-widget__group-label {
        	 height: 100%;
        }
         .bsub-widget__group-discount-summary {
        	 font-size: 12px;
        }
         .bsub-widget__plans-container, .bsub-widget__options-container {
        	 animation: bsub-fadeInFromNone 100ms ease-in-out;
        	 margin-top: 24px;
        }
         .bsub-widget__plans-container input[type='radio'], .bsub-widget__options-container input[type='radio'] {
        	 display: none;
        }
         .bsub-widget__plans-container fieldset + fieldset, .bsub-widget__options-container fieldset + fieldset {
        	 margin-top: 10px;
        }
         .bsub-widget__plan, .bsub-widget__option {
        	 width: 100%;
        }
         .bsub-widget__plan + .bsub-widget__plan, .bsub-widget__option + .bsub-widget__option {
        	 margin-top: 5px;
        }
         .bsub-widget__plan-header {
        	 display: flex !important;
        	 align-items: center;
        	 padding: 6px;
        	 border-radius: 8px;
        }
         .bsub-widget__plan-header .bsub-widget__image {
        	 width: 20px;
        	 height: 20px;
        	 margin-right: 8px;
        }
         .bsub-widget__plan-header .bsub-widget__text {
        	 flex-grow: 1;
        }
         input:checked + .bsub-widget__plan-header {
        	 font-weight: 700;
        	 color: #7dba63;
        	 background: rgba(125, 186, 99, 0.07);
        }
         input:checked + .bsub-widget__plan-header .bsub-widget__unchecked-icon {
        	 display: none;
        }
         input:not(:checked) + .bsub-widget__plan-header .bsub-widget__checked-icon {
        	 display: none;
        }
         input:not(:checked) + .bsub-widget__plan-header:hover {
        	 background: rgba(0, 0, 0, 0.03);
        }
         .bsub-cart__selling-plan-details, .bsub-cart-popup__selling-plan-details {
        	 font-size: 12px;
        }
        
    6. Select Save.

 

Step 2: Edit product.liquid

  1. Under Templates, select product.liquid. This code may be under product-template.liquid (IE. the product.liquid contains {% section ‘product-template’ %}) depending on your theme.
  2. Add this code where you want the widget to display. This normally is placed above the Add to Cart button, or the Quantity Selector:

    {% render 'bsub-widget' %}

    Note: The widget must be included within a <form> or { % form %}, otherwise the widget will not appear.

    Add the widget

  3. Select Save.

If you would like to move the subscription widget up or down the product page, you can do so by moving the line of code mentioned above either up or down the liquid file between the <form> or { % form %} start and end lines.

 

Step 3: Edit cart.liquid

  1. Under Templates, select cart.liquid. It may redirect you to cart-template.liquid depending on your theme.
  2. Find the beginning of the cart properties loop in the file:

    Find the cart lop code

  3. Add the following code within the cart loop:

    {% render 'bsub-cart' %}

    Paste the code

  4. Select Save.

 

Step 4: Edit theme.liquid

  1. Under Layout, select theme.liquid
  2. Find the end of the head tag. This should look like </head> in the file:

    Find the ending of the head tag

  3. Add the following code above the closing </head> tag:

    {{ 'bsub.js' | asset_url | script_tag }} 
    {{
    'bsub.css' | asset_url | stylesheet_tag }}

    theme.liquid_css_and_js

  4. Select Save.

 

Step 5: Edit customers/account.liquid

As customers must log in to manage their subscriptions, this step shows you how to place the Manage Subscriptions link in a visible area inside of the Shopify customer account page, however, any location can be utilized.

When using passwordless login, it’s recommended to add the link to your navigation menu. For detailed instructions, please visit Passwordless login in Subscriptions for Shopify Checkout. You can activate this at a later time.

  1. Under Templates, select customers/account.liquid.

    Select Customers Account Liquid

  2. Under the line:

    {{ 'customer.account.details' | t }}

    or:

    {{ 'customer.account.title' | t }}

    copy and paste the following code:

    <p><a href="/pages/manage-subscriptions" class="text-link">Manage Subscriptions</a></p>

    If you are not seeing either of the codes above, you can add this under the </header>.
    Debut code example for placing the Manage Subscriptions link

  3. Select Save.

 

Step 6: Add the customer portal page

  1. From the Shopify admin, navigate to Sales channels Online Store > Pages.
  2. Select Add page.

  3. Add Manage Subscriptions to the title of the page.

  4. Select the Show HTML button:

    Show HTML

  5. Add the following line of code to the HTML input box:
    <div id="customer-portal-root"></div>

    HTML input box

  6. Select Save.

 


 

Manual install via app widget block (Online Store 2.0 themes)

Note: If you are unsure of whether you are using a vintage or Online Store 2.0 theme, please visit Shopify’s article Theme architecture versions.

Step 1: Install the app widget block

  1. From the Shopify admin, navigate to Sales channels Online Store > Themes.
  2. Next to your chosen them, select Customize.

    Customize

  3. Select Products, then select the template you want to edit.

    Note: Subscription products must be added to a subscription group in order to display a preview of the widget. 

    Product Template

  4. In the left menu, navigate to Product information, then click Add block.
  5. Click the Bold Subscriptions widget to add it.

    Add block

  6. Click Save.

For more Subscription widget app block styling options, please visit Customize the Subscription Widget App Block.

 

Step 2: Place the Manage Subscriptions link

As customers must log in to manage their subscriptions, this step shows you how to place the Manage Subscriptions link in a visible area inside of the Shopify customer account page, however, any location can be utilized.

When using passwordless login, it’s recommended to add the link to your navigation menu. For detailed instructions, please visit Passwordless login in Subscriptions for Shopify Checkout. You can activate this at a later time.

  1. From the Shopify admin, navigate to Sales channels Online Store.
  2. Click the ellipsis next to your chosen theme and select Edit code.
    Edit code
  3. Under Sections, select main-account.liquid.

    main-account.liquid

  4. Under the line:

    {{ 'customer.account.details' | t }}

    or:

    {{ 'customer.account.title' | t }}

    copy and paste the following code:

    <p><a href="/pages/manage-subscriptions" class="text-link">Manage Subscriptions</a></p>

    Manage Subscriptions link

  5. Select Save.

 

Step 3: Add the Customer Portal page

  1. From the Shopify admin, navigate to Sales channels > Online Store > Pages.
  2. Select Add page.

  3. Add Manage Subscriptions to the title of the page.

  4. Select the Show HTML button:

    Show HTML

  5. Add the following line of code to the HTML input box:
    <div id="customer-portal-root"></div>

    HTML input box

  6. Select Save.

 


 

Verify the Bold Subscriptions Installation

Before continuing, please ensure that you have completed one of the installation methods above. Please know that a subscription group will have to be created on your store to test this further.

  • Within the Shopify admin, select Products and then select a product that is already included within a subscription group. You should see a section within the item page that has selling plans, and Bold Subscriptions will be listed within the frequency options.
  • Navigate to your storefront and find a subscription product. The widget should appear on your product page.
  • Test adding the product to your cart. The order frequency should appear within the cart page.
  • Take the product to the checkout and purchase the subscription. You can then review the Bold Subscriptions app customers to see if that order is now listed.
  • On your storefront, log in to a customer account for subscriptions. On the account page, you should see a Manage Subscriptions link.

 


 

Code removal

The Liquid code can be removed by deleting the snippet files, asset files, and the code includes mentioned in the instruction steps above, or you can request a code removal by contacting our Customer Success team.

To remove Bold Subscriptions from your Shopify admin, please follow Uninstall an App from Your Store.

Note: If you are experiencing any issues on the storefront after completing the steps above, we recommend requesting an expert installation. Your theme may require further Liquid code changes. See Request an expert install above for more information.