Make a selection

Manual Installation Guide for Subscriptions for Shopify Checkout

Laurel
Laurel
  • Updated

Bold Subscriptions for Shopify Checkout offers two different methods to help you activate subscriptions on your store. You can use the in-app onboarding flow outlined in Activate Subscriptions for Shopify Checkout or follow the manual install process using the steps outlined in this article.

To manually install Bold Subscriptions on your store, please follow the steps relevant to your Shopify theme.

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.

Pro-Tip: The manual install for vintage themes requires Liquid code to be inserted to your theme. If you are not comfortable working with Liquid code, please move through the steps in Activate Subscriptions for Shopify Checkout to request that our team completes the installation for you.

 


 

Video Walkthrough

 


 

Manual install for Online Store 2.0 themes

Step 1: Enable the Bold Subscriptions app embed

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

    Customize

  3. Click the app embeds icon in the left menu.

    App embed icon

  4. Enable the Bold Subscriptions app embed by moving the toggle to the right.

    Bold Subscriptions App embed

 

Step 2: Install the app widget block

  1. Click the sections icon in the left menu.
  2. In the menu at the top of the page, select Products, then select the product template you want to edit.

    Product Template

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

    Add block

  5. Optional: To see a live preview of the storefront subscription widget, switch your Default product to one that belongs to an active subscription group. In the top left corner of the theme customizer, next to Preview, click Change and select a subscription product.

    Change Default product

  6. Drag and drop the Subscription widget to your preferred location on the page.

    Drag and drop Subscription widget

  7. Click Save.

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

 

Step 3: Create the customer portal page

Alert: This step is only necessary if you installed Bold Subscriptions on your store before December 11, 2023. This page is created automatically for those who've installed after the specified date. 

  1. From the Shopify admin, navigate to Sales channels > Online store > Pages.
  2. Click Add page.
  3. Add Manage Subscriptions to the title of the page.
  4. Click the Show HTML button:

    Show HTML button

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

    Add code

  6. Click Save.

 

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.

 


 

Manual install for vintage themes

Note: It’s best to create and work on a duplicate copy of your Shopify theme when installing Liquid code. This allows you to test your installation before making the theme live on your store.

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.

Step 1: Enable the Bold Subscriptions app embed

  1. From the Shopify admin, navigate to Sales channels Online Store > Themes.
  2. Click Customize next to your desired theme.
  3. Click the app embeds icon in the left menu.

    App embeds icon

  4. Enable the Bold Subscriptions app embed by moving the toggle to the right.

    Bold Subscriptions app embed toggle

  5. Click Save in the top right-hand corner.
  6. Click Exit in the top left-hand corner to return to your Shopify admin.

 

Step 2: Add the liquid files

Alert: 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. 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 > Themes.
  2. Click the ellipsis next to your desired theme, and select Edit code.
  3. Under Snippets, find the following files:
    • bsub-widget.liquid
    • bsub-cart.liquid

      The snippet files

  4. 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. Click Save
  5. Under Assets, find the following files:
    • bsub.js
    • bsub.css

      Asset files

  6. 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. Click Save.

 

Step 3: Edit product.liquid

  1. Under Templates, select product.liquid. This code may be under product-template.liquid (i.e., 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. Click 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 4: 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. Click Save.

 

Step 5: 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. Click Save.

 

Step 6: Create the customer portal page

Alert: This step is only necessary if you installed Bold Subscriptions on your store before December 11, 2023. This page is created automatically for those who've installed after the specified date. 

  1. From the Shopify admin, navigate to Sales channels > Online store > Pages.
  2. Click Add page.
  3. Add Manage Subscriptions to the title of the page.
  4. Click the Show HTML button:

    Show HTML button

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

    Add code

  6. Click Save.

 

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. Click Save.

 


 

Verify the installation

To verify the installation after completing one of the installation methods above, you can either purchase a subscription yourself and then issue yourself a refund, or use Shopify Payments in test mode. For more information, please visit Set up Subscriptions for Shopify Checkout.

 


 

Uninstall

The app embeds can be turned off or Liquid code can be removed by going through the steps above to find their location. If you'd like help removing Liquid code from your theme files, please reach out to our Customer Success team.

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