New features available! Check the changelog
Subscribfy

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)

KeyMeaning
status"1" = VIP plan enabled
dp"1" = VIP discount perk enabled
dvVIP discount %, e.g. 10
datDiscount applies to: all, collection or product
dcollectionCollection IDs the discount applies to (when dat = collection)
dproductProduct IDs the discount applies to (when dat = product)
product_variant_idVIP membership product variant ID
plan_idVIP selling plan ID
product_priceVIP membership price
tier_nameDisplay label, e.g. VIP
discount_triggerWhen 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:

PropertyMeaning
_no_vip_discountA non-empty value excludes the line. See Cart Attributes
_custom_bundle_keyA non-empty value marks a bundle line, which is excluded

Expose the plan settings to your theme JavaScript once, for example in theme.liquid:

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

datCovered when
allEvery product
collectionThe product is in one of dcollection
productThe product is in dproduct

A product tagged NO-VIP-DISCOUNT is never covered.

snippets/vip-covered.liquid
{%- 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.

snippets/vip-price.liquid
{%- 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 -%}
Call from your theme's variant change event
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
  • Clicking Join adds the VIP product to the cart:
    • variant = product_variant_id
    • selling plan = plan_id
    • quantity = 1
  • 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.

Join
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.
Continue as non-member
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:

RuleSkip the line when
Product coveredIt fails the Flow 1 check (including NO-VIP-DISCOUNT)
Not the membershipIt is the membership variant (product_variant_id)
Subscription linesIt has a selling plan and stack_with_subs is not "1"
Line propertiesIt 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_triggerDiscount applies whenShow VIP prices in
both (default)The VIP product is in the cart or the customer has the active VIP tagState A and State C
product_in_cartThe VIP product is in the cartState C
active_memberThe customer has the active VIP tagState 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


Was this page helpful?

On this page

AI Chat

Ask a question about Subscribfy