VIP Membership in a Custom Liquid Theme
Build the VIP product price, the cart join widget, and the checkout handoff directly in a Liquid theme, using Subscribfy's metafields and tags.
Build the VIP Membership experience directly in your Liquid theme: a VIP price on product pages, a join widget in the cart, and a checkout that applies the real discount.
This is a developer guide for stores that want full control over the VIP UI instead of using Subscribfy's app embed and theme blocks. To use the ready-made widget instead, see VIP Product Widget.
Before building
Turn off the Subscribfy app embed and any Subscribfy app blocks in the theme editor (Online Store > Themes > Customize), so they don't render a second widget next to yours.
The app embed also renders other Subscribfy cart features, such as the store credit block. Rebuild any of those you still need.
What You'll Build
Flow 1: Product page VIP price
A VIP price block under the main product price, updated when the shopper changes the variant.
Flow 2: Cart widget
A three-state widget that lets shoppers join VIP, or confirms they are already a member.
Flow 3: Checkout
The normal theme checkout. Subscribfy's Shopify Function applies the actual discount.
Scope
This guide covers the VIP plan and its percentage discount. A second (Elite) plan uses exison_plan_settings2 and customer_subscription2 with the same structure. Other perks (free shipping, welcome offer, discount steps, store credits) are not covered here.
Where the Data Comes From
exison.exison_plan_settings (JSON, VIP plan config)
| Key | Meaning |
|---|---|
status | "1" = VIP plan enabled |
dp | "1" = VIP discount perk enabled |
dv | VIP discount %, e.g. 10 |
dat | Discount applies to: all, collection or product |
dcollection | Collection IDs the discount applies to (when dat = collection) |
dproduct | Product IDs the discount applies to (when dat = product) |
product_variant_id | VIP membership product variant ID |
plan_id | VIP selling plan ID |
product_price | VIP membership price |
tier_name | Display label, e.g. VIP |
discount_trigger | When checkout applies the discount: both (default), product_in_cart or active_member. See Flow 3 |
stack_with_subs | "1" = the VIP discount also applies to subscription cart lines |
For the full list of keys, see Shopify Metafields.
Active VIP: customer tag {customer_tag}: Active, e.g. EXM: VIP: Active.
The same status is also in the customer metafield exison.customer_subscription1.status = "ACTIVE".
The checkout Function checks the tag. Open an active member in Shopify admin to confirm the exact tag your store uses.
Tag NO-VIP-DISCOUNT excludes the product from the VIP discount.
These line item properties exclude a cart line from the VIP discount at checkout:
| Property | Meaning |
|---|---|
_no_vip_discount | A non-empty value excludes the line. See Cart Attributes |
_custom_bundle_key | A non-empty value marks a bundle line, which is excluded |
Expose the plan settings to your theme JavaScript once, for example in theme.liquid:
<script>
window.vipPlan = {{ shop.metafields.exison.exison_plan_settings | json }};
</script>Flow 1: Product Page VIP Price
Check the plan and the discount perk
The VIP plan must be enabled (status = "1") and the discount perk must be on (dp = "1").
Check the product is covered by the discount
dat | Covered when |
|---|---|
all | Every product |
collection | The product is in one of dcollection |
product | The product is in dproduct |
A product tagged NO-VIP-DISCOUNT is never covered.
{%- liquid
assign vip = shop.metafields.exison.exison_plan_settings
assign vip_covered = false
if vip.status == "1" and vip.dp == "1"
case vip.dat
when "all"
assign vip_covered = true
when "collection"
for c in product.collections
if vip.dcollection contains c.id
assign vip_covered = true
break
endif
endfor
when "product"
if vip.dproduct contains product.id
assign vip_covered = true
endif
endcase
endif
assign product_tags = product.tags | join: ',' | upcase
if product_tags contains 'NO-VIP-DISCOUNT'
assign vip_covered = false
endif
-%}Show the VIP price block
If both checks pass, show a VIP price block under the main product price.
Example: dv = 10 and price $50, so the block shows "$45 VIP price". Calculate from the variant's current price, since that is the price checkout discounts.
Checkout uses only the whole-number part of dv (10.5 becomes 10), so the snippet below does the same.
Update on variant change
When the shopper changes the variant, update the VIP price to match. One way is to render a VIP price per variant and swap it from your theme's variant change event.
{%- if vip_covered -%}
{%- assign vip_pct = vip.dv | times: 1.0 | floor -%}
{%- assign current = product.selected_or_first_available_variant -%}
{%- assign current_off = current.price | times: vip_pct | divided_by: 100.0 -%}
<div class="vip-price"
{% for v in product.variants %}
{% assign vip_off = v.price | times: vip_pct | divided_by: 100.0 %}
{% assign vip_cents = v.price | minus: vip_off | round %}
data-vip-price-{{ v.id }}="{{ vip_cents | money | strip_html | escape }}"
{% endfor %}
>
<span class="vip-price__amount">{{ current.price | minus: current_off | round | money }}</span>
{{ vip.tier_name }} price
</div>
{%- endif -%}function updateVipPrice(variantId) {
const block = document.querySelector('.vip-price');
const price = block?.getAttribute(`data-vip-price-${variantId}`);
if (price) block.querySelector('.vip-price__amount').textContent = price;
}Flow 2: Cart Widget
Show the widget only when the VIP plan is enabled (status = "1"). It has three states.
Values such as tier_name, dv and product_price are filled in from exison_plan_settings, for example when listing the perks.
The customer is an active VIP (customer tag).
- Show a member widget, e.g. "You're a
{tier_name}, your{dv}% discount is applied". - No join / not join options.
- If the VIP product is in the cart, remove it, because the customer is already a member.
- Discounted cart lines show the VIP price.
Not an active VIP, VIP product not in cart.
- Show two options, with Continue as non-member selected:
- Join
{tier_name}for{product_price}and save{dv}% - Continue as non-member
- Join
- Clicking Join adds the VIP product to the cart:
- variant =
product_variant_id - selling plan =
plan_id - quantity =
1
- variant =
- After adding, the widget switches to State C.
The selling plan is mandatory
Without selling_plan, the membership is a one-time purchase, not a subscription, and the customer never becomes VIP.
await fetch('/cart/add.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
items: [{
id: window.vipPlan.product_variant_id,
quantity: 1,
selling_plan: window.vipPlan.plan_id,
}],
}),
});
// Then refresh the cart and the widget.Not an active VIP, VIP product in cart (joined).
- Show the widget as joined, with Join VIP selected.
- Cart lines covered by the discount show the VIP price (see Which cart lines get the VIP price).
- Clicking Continue as non-member removes the VIP product from the cart, and the widget goes back to State B.
await fetch('/cart/change.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: String(window.vipPlan.product_variant_id),
quantity: 0,
}),
});
// Then refresh the cart and the widget.Rules for Every State
- At most one VIP product in the cart, with quantity 1.
- The cart and widget refresh after every add or remove.
Which Cart Lines Get the VIP Price
Use the Flow 1 check, plus the rules the checkout Function applies to cart lines. A line shows the VIP price only when:
| Rule | Skip the line when |
|---|---|
| Product covered | It fails the Flow 1 check (including NO-VIP-DISCOUNT) |
| Not the membership | It is the membership variant (product_variant_id) |
| Subscription lines | It has a selling plan and stack_with_subs is not "1" |
| Line properties | It has _no_vip_discount or _custom_bundle_key |
Match the discount trigger
Only show VIP prices in a state where checkout will apply them. With discount_trigger = product_in_cart, active members (State A, VIP product removed) get no discount. With active_member, a shopper who joins (State C) gets no discount on this order. See Flow 3.
Flow 3: Checkout
- Use the normal theme checkout.
- The actual VIP discount is applied by a Shopify Function created by Subscribfy. The theme only displays prices.
- At checkout, the discount is labeled with your
tier_name. - After the first order, the customer gets the active VIP tag and metafield, so on the next visit they see State A.
When the Function applies the discount depends on discount_trigger:
discount_trigger | Discount applies when | Show VIP prices in |
|---|---|---|
both (default) | The VIP product is in the cart or the customer has the active VIP tag | State A and State C |
product_in_cart | The VIP product is in the cart | State C |
active_member | The customer has the active VIP tag | State A |
Discount method
This applies when the VIP discount method is Order discount Function or Product discount Function in the VIP perks settings. Both apply the trigger and cart line rules on this page.
Troubleshooting
Related
Shopify Metafields
Full reference of exison_plan_settings and customer metafields.
VIP Product Widget
The ready-made theme block, if you don't need a custom build.
Membership Perks
Configure the VIP discount, its products and collections.
Cart Attributes
Exclude specific cart lines from the VIP discount.
Was this page helpful?