Skip to main content

Product Subscription Widget - Theme Integration

Add subscription plan selector to product pages. Developer guide for theme integration.

Updated over 3 weeks ago

Product Subscription Widget - Theme Integration

Add a subscription plan selector directly on your product pages. Customers can choose between one-time purchase and subscribe & save options before adding to cart.


Overview

The Product Subscription Widget displays selling plan options (delivery frequencies, discounts) on product pages. It automatically detects Subscribfy selling plans and shows customers their purchase options with pricing.

Features:

  • One-time purchase vs Subscribe & Save toggle

  • Automatic discount calculation and display

  • Multiple delivery frequency options

  • Two layout styles: dropdown or tiles

  • Variant switching support


Quick Setup

Step 1: Create the Widget Snippet

In your Shopify admin, go to Online StoreThemesEdit code

Create a new snippet: snippets/subscribfy-product-subscription-widget.liquid

Copy the template code from the Complete Code section below.

Step 2: Add Widget to Product Page

Find your product template (usually sections/main-product.liquid or product-template.liquid)

Insert this code where you want the widget to appear (typically above the Add to Cart button):

{% if request.page_type == 'product' %}
  {%- render 'subscribfy-product-subscription-widget',
      product: product,
      form_id: form_id,
      start_onetime: false,
      display_interval_selector: true,
      delivery_frequency_layout: "select-list" -%}
{% endif %}

Step 3: Add Custom Script (Optional)

For stores with membership widgets, create snippets/subscribfy-custom.liquid and add this line near the end of your theme.liquid file:

{% render 'subscribfy-custom' %}

See the Complete Code section for the custom script content.


Widget Options

Configure the widget by passing parameters when rendering:

Parameter

Type

Default

Description

product

object

required

The product object. Required when not on main product page.

form_id

string

auto

ID of the product form. Required when widget is outside main template.

start_onetime

boolean

true

If true, "One Time" is selected by default. If false, subscription is selected.

display_interval_selector

boolean

true

Show/hide the delivery frequency dropdown or tiles.

delivery_frequency_layout

string

"select-list"

Layout style: "select-list" (dropdown) or "tiles" (clickable boxes).


Delivery Frequency Layouts

Select List (Default)

A dropdown menu for choosing delivery frequency:

delivery_frequency_layout: "select-list"

Best for: Products with many frequency options (5+)

Tiles

Clickable boxes showing all options at once:

delivery_frequency_layout: "tiles"

Best for: Products with few frequency options (2-4)


Change Default Selection

To make "One-time Purchase" the default instead of subscription:

  1. Go to Online StoreEdit Code

  2. Search for subscribfy-product-subscription-widget

  3. Find the render statement in your product template

  4. Change start_onetime: false to start_onetime: true

Note: If the product requires a selling plan (subscription-only product), the subscription option will always be selected regardless of this setting.


Customization

CSS Classes

Style these classes to match your theme:

Class

Element

.exm_selling-plan-wrapper

Main container

.selling-plan-label

Option card (one-time or subscription)

.exm_option--active

Currently selected option

.sp_offer_price

Subscription price display

.sp_offer_reg_price

Regular (crossed out) price

.exm_option__discount

Discount badge (e.g., "Save 15%")

.exm_selling-plan-interval_dropdown

Frequency dropdown

.exm_selling-plan-interval_tile

Frequency tile (when using tiles layout)

.interval_tile_is_selected

Selected frequency tile

Color Customization

The default colors in the template:

  • Active border: #242424 (dark gray)

  • Discount badge: #0fa573 (green)

  • Checkmark: #ffbd59 (gold)

Edit the <style> section in the widget template to change these.


Troubleshooting

Widget not showing?
Check that the product has Subscribfy selling plans assigned. The widget only appears for products with active selling plans.

Prices not updating on variant change?
Ensure the form_id parameter matches your theme's product form ID. Check browser console for JavaScript errors.

Duplicate widgets appearing?
The script automatically removes duplicates. If you see multiple widgets, check that you haven't included the render statement twice.

Widget conflicts with membership widget?
Add the subscribfy-custom.liquid script to hide the membership widget when product subscriptions are in the cart.


Complete Code

subscribfy-product-subscription-widget.liquid (V4.8 - 04.2025)

Latest version with tiles layout support. Copy this entire code block:

{% comment %}
V4.8 - 04.2025

product: required on non-product pages, snippets only have access to globally available variables
by default. If the product variable is defined locally, for example inside a for each product in
a collection, the snippet will not have access to the product variable unless explicitly passed.

form_id: required when plan picker is NOT redered on main product template page

start_onetime: optional to choose initial selection, defaults to true

display_interval_selector: optional, set to false to hide `exm_selling-plan-interval_wrapper`, defaults to true

delivery_frequency_layout: choose how the delivery frequency is displayed. Possible values: "select-list" (default) or "tiles"

In the sections/main-product.liquid file (note: this file may have a different name in some themes),
manually insert the code below at the location where you'd like the plan selector widget to display:

{% if request.page_type == 'product' %}
{%- render 'subscribfy-product-subscription-widget', product: product, form_id: form_id, start_onetime: false, display_interval_selector: true, delivery_frequency_layout: "tiles" -%}
{% endif %}
{% endcomment %}

{% assign current_variant = product.selected_or_first_available_variant | default: product.variants.first %}

{% assign selling_plan_selected = blank %}
{% assign has_selling_plans = false %}

{% for variant in product.variants %}

{% for allocation in variant.selling_plan_allocations %}
{% for option in allocation.selling_plan.options %}
{% if option.value contains 'Subscribfy' %}
{% assign has_selling_plans = true %}
{% endif %}
{% endfor %}
{% endfor %}

{% endfor %}

{% liquid
if product.selected_selling_plan
assign selling_plan_selected = product.selected_selling_plan.id
else
for allocation in current_variant.selling_plan_allocations
unless allocation.selling_plan.options[0].value contains 'Subscribfy'
continue
endunless
assign selling_plan_selected = allocation.selling_plan.id
break
endfor
endif
%}

{% liquid
unless form_id
assign form_id = 'product-form-' | append: section.id
endunless

if start_onetime == null
assign start_onetime = true
endif
if product.requires_selling_plan
assign start_onetime = false
endif
if product.selected_selling_plan
assign start_onetime = false
endif

if display_interval_selector == null
assign display_interval_selector = true
endif

if delivery_frequency_layout == null or delivery_frequency_layout == blank
assign delivery_frequency_layout = 'select-list'
endif
%}

{% assign exm_subProd_widget_template = 2 %}
{% if exm_subProd_widget_template == 2 %}
{% if has_selling_plans and product.id != shop.metafields.exison.exison_plan_settings.product_gid %}
<div
class="exm_selling-plan-wrapper"
data-form="{{ form_id }}"
data-start_onetime="{{ start_onetime }}"
data-display_interval_selector="{{ display_interval_selector }}"
>
{% for variant in product.variants %}
<div
class="exm_selling-plan-fieldset {% if variant.id != product.selected_or_first_available_variant.id %}exm-selling-plan-hide{% endif %} sp-fieldset-vid_{{ variant.id }}"
data-id=""
>
{% unless product.requires_selling_plan %}
<label
for="selling_plan__one-time-purchase-option__vid_{{ variant.id }}"
data-variant="{{ variant.id }}"
class="selling-plan-label one-time-purchase-option {% if start_onetime == true %} exm_option--active {% endif %} "
>
<div class="sp-offer-topline">
<input
type="radio"
name="selling_plan"
class="exm_widget__option__input"
id="selling_plan__one-time-purchase-option__vid_{{ variant.id }}"
value=""
{%- if start_onetime == true and variant.id == product.selected_or_first_available_variant.id -%}
checked="checked"
{%- endif -%}
{%- if form_id -%}
form="{{ form_id }}"
{%- endif -%}
>
<span class="sp_offer_label">One Time </span>
<span class="sp_offer_reg_price">{{ variant.price | money }} </span>
</div>

<div class="sp-offer-content"></div>
</label>
{% endunless %}

<div
class="exm_selling-plans {% if start_onetime == false and variant.id == product.selected_or_first_available_variant.id %} exm_option--active {% endif %}"
data-variant="{{ variant.id }}"
>
{% for allocation in variant.selling_plan_allocations %}
{% unless allocation.selling_plan.options[0].value contains 'Subscribfy' %}
{% comment %} NOT from Subscribfy, skip to the next {% endcomment %}
{% continue %}
{% endunless %}

{% assign selling_plan_id = allocation.selling_plan.id | times: 1 %}

{% assign initial_sp_discount_text = 'Subscribe now' %}
{% assign then_sp_discount_text = blank %}
{% assign amount_discounted_percentage = blank %}

{%- if allocation.selling_plan.price_adjustments -%}

{% assign initial_adjustment = allocation.selling_plan.price_adjustments[0] %}
{% assign then_adjustment = allocation.selling_plan.price_adjustments[1] %}

{% assign initial_value = initial_adjustment.value %}
{% assign then_value = then_adjustment.value %}
{% assign initial_type = initial_adjustment.value_type %}
{% assign then_type = then_adjustment.value_type %}

{% if initial_type == 'price' %}
{% assign amount_discounted = variant.price | minus: initial_value %}
{% assign variant_price = variant.price | times: 1.0 %}
{% assign amount_discounted_percentage = amount_discounted | times: 100 | divided_by: variant_price | round %}
{% assign initial_display_value = amount_discounted | money %}
{% else %}
{% assign amount_discounted_percentage = initial_value | round %}
{% assign initial_display_value = amount_discounted_percentage | append: '%' %}
{% endif %}

{% if then_type == 'price' %}
{% assign then_display_value = then_value | money %}
{% else %}
{% assign then_display_value = then_value | round | append: '% OFF' %}
{% endif %}

{% if initial_value > 0 and initial_adjustment.order_count == blank %}
{% capture initial_sp_discount_text %}Subscribe and save <span class="sp_price_adjustments">{{ initial_display_value }}</span>{% endcapture %}
{% elsif initial_value > 0 and initial_adjustment.order_count > 0 %}
{% capture initial_sp_discount_text %}Subscribe and save {{ initial_display_value }} on first {{ initial_adjustment.order_count }} payments.{% endcapture %}
{% endif %}

{% if then_value > 0 and initial_adjustment.order_count > 0 %}
{% capture then_sp_discount_text %}Then {{ then_display_value }} {% endcapture %}
{% elsif then_value == 0 and initial_adjustment.order_count > 0 %}
{% capture then_sp_discount_text %}Then regular price{% endcapture %}
{% elsif initial_value == 0 and then_value > 0 and initial_adjustment.order_count > 0 %}
{% capture then_sp_discount_text %}Regular price on the first {{ initial_adjustment.order_count }} payments. Then {{ then_display_value }} {% endcapture %}
{% endif %}


{%- endif -%}

<label
class="
selling-plan-label subscription-purchase-option start_onetime___{{ start_onetime }} selling_plan_selected___{{ selling_plan_selected }}

{% if start_onetime == false and selling_plan_selected == selling_plan_id and variant.id == product.selected_or_first_available_variant.id %} exm_option--active {% endif %}
spo_{{ allocation.selling_plan.id }}_vid_{{ variant.id }}
"
style="{%- if selling_plan_selected == selling_plan_id and variant.id == product.selected_or_first_available_variant.id -%} {%- else -%}display:none;{%- endif -%} "
>
<div class="sp-offer-topline">
<input
type="radio"
name="selling_plan"
class="exm_widget__option__input"
id="selling_plan_{{ allocation.selling_plan.id }}_vid_{{ variant.id }}"
value="{{ selling_plan_id }}"
{%- if start_onetime == false
and selling_plan_selected == selling_plan_id
and variant.id == product.selected_or_first_available_variant.id
-%}
checked="checked"
{%- endif -%}
data-price="{{ allocation.per_delivery_price | money_with_currency }}"
{%- if form_id -%}
form="{{ form_id }}"
{%- endif -%}
>

{%- if initial_sp_discount_text != blank or then_sp_discount_text != blank -%}
<div class="sp-offer-incentive">
{{ initial_sp_discount_text }}
{{ then_sp_discount_text }}

<br>

{%- if allocation.selling_plan.price_adjustments[0].value > 0 -%}
<span class="exm_option__discount exm__widget__option__discount" data-label-discount="">
Save {{ amount_discounted_percentage }}%</span
>
{%- endif -%}
</div>
{%- endif -%}

{% if current_variant.price > allocation.per_delivery_price %}
<span class="sp_offer_reg_price">{{ current_variant.price | money }} </span>
{% endif %}
<span class="sp_offer_price">{{ allocation.per_delivery_price | money }} </span>
</div>

<div class="sp-offer-content">
<div class="sp-offer-content-body">
{% if amount_discounted_percentage > 0 %}
<span>
<span class="sp-checkmark-exm"></span> Save
{{ amount_discounted_percentage }}% on all subscriptions orders
</span>
{% endif %}
<span> <span class="sp-checkmark-exm"></span> Pause or cancel anytime </span>
<span> <span class="sp-checkmark-exm"></span> Delivery every {{ allocation.selling_plan.name }} </span>
</div>
</div>

</label>

{% endfor %}

{% if delivery_frequency_layout == "tiles" %}
<div class="exm_selling-plan-interval_wrapper__tiles">
<div class="label_delivery_every">Delivery every:</div>

<div
class="exm_selling-plan-interval_block__tiles"
>
<div
id="exm_selling_plan_vid_{{ variant.id }}"
class="exm_selling-plan-interval_tiles"
name="exm_selling_plan_vid_{{ variant.id }}"
>
{% for allocation in variant.selling_plan_allocations %}
{% unless allocation.selling_plan.options[0].value contains 'Subscribfy' %}
{% comment %} NOT from Subscribfy, skip to the next {% endcomment %}
{% continue %}
{% endunless %}

{% assign selling_plan_id = allocation.selling_plan.id | times: 1 %}
<div
class="exm_selling-plan-interval_tile
{% if selling_plan_selected == selling_plan_id and variant.id == product.selected_or_first_available_variant.id%} interval_tile_is_selected {% endif %} "
data-purchase-option="spo_{{ selling_plan_id }}_vid_{{ variant.id }}"
data-checkbox="selling_plan_{{ selling_plan_id }}_vid_{{ variant.id }}"
data-value="{{ selling_plan_id }}"
>
{{ allocation.selling_plan.name }}
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}

</div>
</div>
{% endfor %}


{% if delivery_frequency_layout == "select-list" %}
<div
class="exm_selling-plan-interval_wrapper"
style="{% if start_onetime == true or display_interval_selector == false %} display: none;{% endif %}"
>
{% for variant in product.variants %}
<div
class="exm_selling-plan-interval_block {% if variant.id != product.selected_or_first_available_variant.id %}exm-selling-plan-hide{% endif %}"
data-variant="{{ variant.id }}"
>
<select
id="exm_selling_plan_vid_{{ variant.id }}"
class="exm_selling-plan-interval_dropdown"
name="exm_selling_plan_vid_{{ variant.id }}"
>
{% for allocation in variant.selling_plan_allocations %}
{% unless allocation.selling_plan.options[0].value contains 'Subscribfy' %}
{% comment %} NOT from Subscribfy, skip to the next {% endcomment %}
{% continue %}
{% endunless %}
{% assign selling_plan_id = allocation.selling_plan.id | times: 1 %}
<option
{% if selling_plan_selected == selling_plan_id
and variant.id == product.selected_or_first_available_variant.id
%}
selected
{% endif %}

data-plan-option="spo_{{ allocation.selling_plan.id }}_vid_{{ variant.id }}"
value="{{ allocation.selling_plan.id }}"
>
{{ allocation.selling_plan.name }}
</option>
{% endfor %}
</select>
</div>
{% endfor %}
</div>
{% endif %}

</div>

<br>
{% endif %}
{% endif %}

<style>
.exm_selling-plan-fieldset {
display: flex;
flex-direction: column;
gap: 10px;
color: #090909;
position: relative;
}
.exm_selling-plan-fieldset input[name="selling_plan"]{
scale: 1.7;
accent-color: #000;
margin-right: 20px;
}
.exm-selling-plan-hide{
display:none !important;
}

.subscription-purchase-option:not(.exm_option--active) .sp-offer-content,
.subscription-purchase-option:not(.exm_option--active) .sp-offer-interval{
display: none;
}

.selling-plan-label.one-time-purchase-option,
.exm_selling-plans{
font-size: 15px;
display: flex;
flex-direction: column;
margin-right: 4px;
box-shadow: 0 0 5px rgba(23, 24, 24, 0.05), 0 1px 2px rgba(0, 0, 0, 0.07);
border-radius: 10px;
padding: 10px;
cursor: pointer;
background: #fff;
}
.selling-plan-label.one-time-purchase-option.exm_option--active,
.exm_selling-plans.exm_option--active{
border: 2px solid #242424;
}

.selling-plan-label .sp-offer-topline{
display: flex;
align-items: center;
width: 100%;
}

.exm_selling-plan-fieldset .selling-plan-label.subscription-purchase-option .sp_offer_label{
font-size: 1.2em;
font-weight: 700;
}

.exm_selling-plan-wrapper .sp_offer_label,
.exm_selling-plan-wrapper .sp-offer-incentive{
font-size: 1.2em;
font-weight: 700;
}

.sp_offer_reg_price{
display: block;
font-weight: 700;
font-size: 1.6em;
margin-left: auto;
}
.sp_offer_price{
font-size: 1.6em;
font-weight: 700;
margin-left: auto;
white-space: nowrap;
}
.exm_selling-plan-fieldset .selling-plan-label.subscription-purchase-option .sp_offer_reg_price{
display:none;
}
.sp-offer-incentive {
font-size: 15px;
}
.sp-offer-content-body {
display: flex;
flex-direction: column;
padding: 10px;
}
span.exm_option__discount.exm__widget__option__discount {
font-weight: 700;
color: #0fa573;
border: 1px #0fa573 solid;
padding: 0px 8px;
border-radius: 10px;
}
.sp_price_adjustments{
display: none;
}

.exm_selling-plan-interval_wrapper{
margin-top: 12px;
}
.exm_selling-plan-interval__label{
display: block;
margin-bottom: 5px;
}
.exm_selling-plan-interval_dropdown{
min-height: 44px;
border-radius: 40px;
border: 1px solid lightgrey;
padding-top: 10px;
padding-left: 18px;
padding-bottom: 10px;
background: #fff;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-position: right center;
background-image: url(https://subscribfy.nyc3.digitaloceanspaces.com/icons/ico-select.svg);
background-repeat: no-repeat;
background-position: right 10px center;
line-height: 1.2;
padding-right: 28px;
text-indent: 0.01px;
text-overflow: '';
cursor: pointer;
background-color: #ffffff;
}

.exm_selling-plan-interval_dropdown:focus,
.exm_selling-plan-interval_dropdown:focus-visible{
border-color: #000000;
outline: unset;
outline-offset: unset;
box-shadow: unset;
}

.sp-checkmark-exm {
background: transparent;
width: 8px;
transform: rotate(40deg);
height: 16px;
display: flex;
border-right: 2px solid #ffbd59;
border-bottom: 2px solid #ffbd59;
display: inline-block;
margin-right: 10px;
}


.exm_selling-plans:not(.exm_option--active) .exm_selling-plan-interval_wrapper__tiles {
display: none;
}

.exm_selling-plan-interval_wrapper__tiles {
max-width: 90%;
margin: 0 auto;
}
.exm_selling-plan-interval_tiles{
display: flex;
justify-content: space-between;
}

.label_delivery_every{
text-align: left;
}
.exm_selling-plan-interval_tile{
font-size: 13px;
display: block;
text-align: center;
margin-right: 4px;
border: 1px solid #ccc;
border-radius: 10px;
padding: 5px 20px;
cursor: pointer;
}

.interval_tile_is_selected{
border: 1px solid #0fa573;
color: #0fa573;
font-weight: bold;
}

</style>


<script>

// Ensure there's only one selling plan wrapper
const exm_sellingPlanElements = document.querySelectorAll('.exm_selling-plan-wrapper');
exm_sellingPlanElements.forEach((element, index) => {
if (index > 0) element.remove(); // Keep the first element, remove the rest
});

const sp_wrapper = document.querySelector('.exm_selling-plan-wrapper'); // Selects the first match
const exm_formAttr = sp_wrapper ? sp_wrapper.getAttribute('data-form') : null;

document.addEventListener('DOMContentLoaded', function () {
if( exm_formAttr ){

spo_observeUrlChange();
spo_observeInputValueChange();

document.body.addEventListener('click', function (e) {

// Event delegation for selling-plan-label clicks
const label = e.target.closest('.exm_selling-plan-fieldset .selling-plan-label');
if (label) {
const input = label.querySelector('input[name="selling_plan"]');
if (input) input.checked = true;

document.querySelectorAll('.selling-plan-label.exm_option--active').forEach(el => {
el.classList.remove('exm_option--active');
});
label.classList.add('exm_option--active');

document.querySelectorAll('.exm_selling-plans.exm_option--active').forEach(el => {
el.classList.remove('exm_option--active');
});

const parentFieldset = label.closest('.exm_selling-plans');
if (parentFieldset) parentFieldset.classList.add('exm_option--active');

if (sp_wrapper.getAttribute('data-display_interval_selector') === 'true') {
const intervalWrapper = document.querySelector('.exm_selling-plan-interval_wrapper');
if (intervalWrapper) {
intervalWrapper.style.display = label.classList.contains('one-time-purchase-option') ? 'none' : 'block';
}
}
}


// Handling clicks on interval tiles
const tile = e.target.closest('.exm_selling-plan-interval_tile');
if (tile) {
document.querySelector('.selling-plan-label.one-time-purchase-option.exm_option--active')?.classList.remove('exm_option--active');
document.querySelectorAll('.interval_tile_is_selected').forEach(el => {
el.classList.remove('interval_tile_is_selected');
});

tile.classList.add('interval_tile_is_selected');

const checkbox = document.getElementById(tile.getAttribute('data-checkbox'));
if (checkbox) checkbox.checked = true;

document.querySelectorAll('.subscription-purchase-option').forEach(option => option.style.display = 'none');

const purchaseOptionClass = tile.getAttribute('data-purchase-option');
if (purchaseOptionClass) {
document.querySelectorAll('.' + purchaseOptionClass).forEach(option => {
option.style.display = 'block';
option.classList.add('exm_option--active');
});
}
}

});


// Event delegation for interval dropdown changes
document.body.addEventListener('change', function (e) {
if (e.target.classList.contains('exm_selling-plan-interval_dropdown')) {
const selectedOption = e.target.selectedOptions[0];
const selectedId = selectedOption ? selectedOption.getAttribute('data-plan-option') : null;

const labels = document.querySelectorAll('.selling-plan-label.subscription-purchase-option');
labels.forEach(function (label) {
label.style.display = 'none';
label.classList.remove('exm_option--active');
});

const selectedLabel = document.querySelector('.' + selectedId);
if (selectedLabel) {
selectedLabel.style.display = 'block';
selectedLabel.click();
}
}
});

}
});

const spo_observeUrlChange = function () {
let oldHref = document.location.href;
const body = document.body;
const observer = new MutationObserver(function () {
if (oldHref !== document.location.href) {
oldHref = document.location.href;
const urlParams = new URLSearchParams(window.location.search);
const variantId = urlParams.get('variant');

exm_sp_variant_changed(variantId);
}
});
observer.observe(body, { childList: true, subtree: true });
};

const spo_observeInputValueChange = function () {
let atcForm = document.getElementById(exm_formAttr);

if (!atcForm) {
atcForm = document.querySelector('form[action*="/cart/add"]');
if (!atcForm) {
console.error('Form with action \'/cart/add\' not found.');
return;
}
}

// Determine the form and the id input/select element
var input = atcForm.querySelector('input[name="id"], select[name="id"]');
if (!input) return;

// Function to handle variant changes
function handleVariantChange(newValue) {
if (newValue !== oldInputValue) {
oldInputValue = newValue;
var variantId = newValue;
exm_sp_variant_changed(variantId);
}
}

var oldInputValue = input.value;

if (input.tagName === 'SELECT') {
// Handle change event for select lists
input.addEventListener('change', function () {
handleVariantChange(input.value);
});
} else {
// Use MutationObserver for input elements
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.type === 'attributes' && mutation.attributeName === 'value') {
handleVariantChange(input.value);
}
});
});

observer.observe(input, { attributes: true });
}

};

let lastProcessedVariantId = null;

const exm_sp_variant_changed = function (variantId) {
if (variantId === lastProcessedVariantId) {
return; // Skip if same variantId already processed
}
lastProcessedVariantId = variantId;

// Hide all selling plan fieldsets and show the relevant one
const fieldsets = document.querySelectorAll('.exm_selling-plan-wrapper .exm_selling-plan-fieldset');
fieldsets.forEach(function (fieldset) {
fieldset.classList.add('exm-selling-plan-hide');
});

const relevantFieldset = document.querySelector('.exm_selling-plan-wrapper .exm_selling-plan-fieldset.sp-fieldset-vid_' + variantId);
if (relevantFieldset) relevantFieldset.classList.remove('exm-selling-plan-hide');

// Select the selling plan option
const selectedValueElement = document.querySelector('#exm_selling_plan_vid_' + variantId);
//default is select list
var selectedValue = selectedValueElement ? selectedValueElement.value : null;

{% if delivery_frequency_layout == "tiles" %}
if (selectedValueElement) {
let selectedTile = selectedValueElement.querySelector('.exm_selling-plan-interval_tile.interval_tile_is_selected');
if (!selectedTile) {
// Fallback to first element & also add class interval_tile_is_selected
selectedTile = selectedValueElement.querySelector('.exm_selling-plan-interval_tile');
}
if (selectedTile) {
selectedTile.classList.add('interval_tile_is_selected');
var selectedValue = selectedTile.getAttribute('data-value');
}
}
{% endif %}


const startOneTime = sp_wrapper.getAttribute('data-start_onetime');

if (startOneTime === 'true') {
const oneTimeLabel = document.querySelector('.selling-plan-label[for="selling_plan__one-time-purchase-option__vid_' + variantId + '"]');
if (oneTimeLabel) oneTimeLabel.click();

const selectedLabel = document.querySelector('.selling-plan-label.spo_' + selectedValue + '_vid_' + variantId);
if (selectedLabel) selectedLabel.style.display = 'block';

const intervalWrapper = document.querySelector('.exm_selling-plan-interval_wrapper');
if (intervalWrapper) intervalWrapper.style.display = 'none';
} else {
const selectedLabel = document.querySelector('.selling-plan-label.spo_' + selectedValue + '_vid_' + variantId);

if (selectedLabel) {
selectedLabel.click();
selectedLabel.style.display = 'block';
}
}

// Plan select list
const intervalBlocks = document.querySelectorAll('.exm_selling-plan-interval_block');
intervalBlocks.forEach(function (block) {
block.classList.add('exm-selling-plan-hide');
});

const relevantIntervalBlock = document.querySelector('.exm_selling-plan-interval_block[data-variant="' + variantId + '"]');
if (relevantIntervalBlock) relevantIntervalBlock.classList.remove('exm-selling-plan-hide');
};

</script>

subscribfy-custom.liquid (Membership Integration)

This script hides the membership widget when product subscriptions are in the cart (prevents conflicts):

{%- assign exm_product_subscription_in_cart = 0 -%}
{%- if cart.item_count > 0 -%}
{%- for item in cart.items -%}

{% comment %} skip if vip membership or Elite montly box {% endcomment %}
{% if item.product.variants[0].id == shop.metafields.exison.exison_plan_settings.product_variant_id or item.product.variants[0].id == shop.metafields.exison.exison_plan_settings2.product_variant_id %}
{% continue %}
{% endif %}

{% if item.selling_plan_allocation.selling_plan.id %}
{% assign exm_product_subscription_in_cart = 1 %}
{%- endif -%}

{%- endfor -%}
{%- endif -%}


<script>
// Subscribfy

window.exmDev = 1;


{% if exm_product_subscription_in_cart == 1 %}
exm_setCookie('exmcart','PAYG',1);
$(document).ready(function() {

$('.exison-widget__Containe_Wrapper').addClass("hide_sp");

$("#setNoExison").trigger("click");

});
{% endif %}

document.addEventListener('subscribfy:updatedExmCartItems', function(evt) {
var vipIsSelected = $('#setExison').hasClass('exm_active');

$.ajax({
type: 'POST',
url: '/cart.js?from=subscribfy',
dataType: 'json',
success: function(cart) {

var productSubscriptionInCart = 0;

$.each(cart.items, function (index, cartItem) {

// skip vip or elite
if (cartItem.variant_id == window.exmData.plan_settings.product_variant_id || cartItem.variant_id == window.exmData.plan_settings2.product_variant_id ) {
return;
}

if( cartItem.selling_plan_allocation ){
//hide VIP widget and remove VIP product from cart
productSubscriptionInCart = 1;

$('.exison-widget__Containe_Wrapper').addClass("hide_sp");

if( vipIsSelected ){
$("#setNoExison").trigger("click");
}
}

});

if( !productSubscriptionInCart ){
$('.exison-widget__Containe_Wrapper').removeClass("hide_sp");
}

}
});

});



// ...




function exm_setCookie(name,value,hours) {
var expires = "";
if (hours) {
var date = new Date();
date.setTime(date.getTime() + (hours*60*60*1000));
expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + (value || "") + expires + "; path=/";
}
function exm_getCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for(var i=0;i < ca.length;i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
}
return null;
}
function exm_eraseCookie(name) {
document.cookie = name +'=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
}

</script>

Optional: Update Add to Cart Button Price

Add to subscribfy-custom.liquid if your theme shows price on the Add to Cart button:

<script>
(function () {
const SELECTORS = {
VIP_WRAPPER: '.exm-pp-vip-wrapper',
VIP_RADIO: '#exm-pp-purchase-vip',
REGULAR_RADIO: '#exm-pp-purchase-regular',
ATC_PRICE: '.product__price--regular',
};

function updateATCPrice() {
const wrapper = document.querySelector(SELECTORS.VIP_WRAPPER);
const vipRadio = document.querySelector(SELECTORS.VIP_RADIO);
const atcPriceEl = document.querySelector(SELECTORS.ATC_PRICE);

if (!wrapper || !atcPriceEl) return;

const variantId = wrapper.getAttribute('data-vid');
if (!variantId) return;

const isVipJoined = wrapper.classList.contains('exm-pp-active-or-joined');
const isVipSelected = vipRadio?.checked;

const vipPrice = wrapper.getAttribute(`data-vip-price-${variantId}`)?.trim();
const regularPrice = wrapper.getAttribute(`data-regular-price-${variantId}`)?.trim();

if ((isVipJoined || isVipSelected) && vipPrice) {
atcPriceEl.textContent = vipPrice;
} else if (regularPrice) {
atcPriceEl.textContent = regularPrice;
}
}

document.addEventListener('DOMContentLoaded', () => {
updateATCPrice();
setTimeout(updateATCPrice, 2000); // Delay by x seconds
});

document.addEventListener('change', function (e) {
if (e.target.name === 'exm_pp_radio') {
updateATCPrice();
}
});

// Also observe mutation to recheck price on class change (joined state may toggle)
const wrapper = document.querySelector(SELECTORS.VIP_WRAPPER);
if (wrapper) {
const observer = new MutationObserver(updateATCPrice);
observer.observe(wrapper, { attributes: true, attributeFilter: ['class'] });
}
})();
</script>

Note: Update SELECTORS.ATC_PRICE to match your theme's price element class.


Contact support

Did this answer your question?