#208 - Display Member Count Per Plan

Display how many members are on each plan directly on your pricing page.

Video Tutorial

tutorial.mov

Watch the video for step-by-step implementation instructions

The Code

272 lines
Paste this into Webflow
<!-- 💙 MEMBERSCRIPT #208 v0.1 💙 DISPLAY MEMBER COUNT PER PLAN -->
<script>
(function() {
  'use strict';

  var CONFIG = {
    planCountAttr: 'ms-code-plan-count',
    planNameCountAttr: 'ms-code-plan-count-name',
    totalMembersAttr: 'ms-code-total-members',
    templateAttr: 'data-ms-template',
    formatAttr: 'data-ms-format',
    endpointAttr: 'data-ms-counts-endpoint',
    loadingText: '...',
    errorText: '-',
    cacheKey: 'ms208_counts',
    cacheTTL: 120000
  };

  // -- Helpers --

  function formatNumber(num, format) {
    if (typeof num !== 'number' || isNaN(num)) return 'number0';
    format = (format || 'comma').toLowerCase();
    if (format === 'raw') return String(num);
    if (format === 'short') {
      if (num >= 1000000) return (num / 1000000).toFixed(1).replace(/\.0$/, '') + 'M';
      if (num >= 1000) return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'K';
      return String(num);
    }
    if (format === 'rounded') {
      if (num >= 1000) return Math.floor(num / 100) * 100 + '+';
      if (num >= 100) return Math.floor(num / 10) * 10 + '+';
      return String(num) + '+';
    }
    try { return num.toLocaleString('en-US'); } catch (e) { return String(num); }
  }

  function applyTemplate(el, count, planName) {
    var format = el.getAttribute(CONFIG.formatAttr) || 'comma';
    var formatted = formatNumber(count, format);
    var template = el.getAttribute(CONFIG.templateAttr);
    if (template) {
      el.textContent = template
        .replace(/\{count\}/g, formatted)
        .replace(/\{name\}/g, planName || '')
        .replace(/\{raw\}/g, String(count));
    } else {
      el.textContent = formatted;
    }
    el.setAttribute('data-ms-state', 'loaded');
    el.setAttribute('data-ms-count-value', String(count));
  }

  // -- Cache --

  function getCache() {
    try {
      var raw = localStorage.getItem(CONFIG.cacheKey);
      if (!raw) return null;
      var data = JSON.parse(raw);
      if (data && data.ts && (Date.now() - data.ts < CONFIG.cacheTTL)) return data;
    } catch (e) {}
    return null;
  }

  function setCache(data) {
    try { localStorage.setItem(CONFIG.cacheKey, JSON.stringify(data)); } catch (e) {}
  }

  function getEndpoint() {
    if (window.MS208_ENDPOINT) return window.MS208_ENDPOINT;
    var el = document.querySelector('[' + CONFIG.endpointAttr + ']');
    return el ? (el.getAttribute(CONFIG.endpointAttr) || '').trim() : null;
  }

  // -- Count members per plan keywordfrom raw members response --

  function countMembersPerPlan(members) {
    var counts = {};
    for (var i = 0; i < members.length; i++) {
      var connections = members[i].planConnections || [];
      for (var j = 0; j < connections.length; j++) {
        var pid = connections[j].planId;
        var pname = connections[j].planName || '';
        if (!pid) continue;
        if (!counts[pid]) counts[pid] = { id: pid, name: pname, memberCount: 0 };
        counts[pid].memberCount++;
      }
    }
    var plans = [];
    for (var key in counts) { if (counts.hasOwnProperty(key)) plans.push(counts[key]); }
    return plans;
  }

  function isMembersResponse(data) {
    var arr = data.data || data;
    if (!Array.isArray(arr) || arr.length === 0) return false;
    return arr[0].planConnections !== undefined || arr[0].auth !== undefined;
  }

  // -- Fetch counts --

  function fetchCounts() {
    var cached = getCache();
    if (cached) return Promise.resolve(cached);

    var endpoint = getEndpoint();
    if (!endpoint) {
      console.warn('MemberScript #number208: No endpoint set. Add data-ms-counts-endpoint or set window.MS208_ENDPOINT');
      return Promise.reject(new Error('No endpoint'));
    }

    return fetch(endpoint, { method: 'GET', headers: { 'Accept': 'application/json' } })
      .then(function(r) {
        if (!r.ok) throw new Error('HTTP ' + r.status);
        return r.json();
      })
      .then(function(data) {
        var result = { ts: Date.now(), plans: [], totalMembers: 0 };

        // Shape number1: Raw members response from Admin API { data: [members], totalCount }
        if (data.data && Array.isArray(data.data) && isMembersResponse(data)) {
          result.plans = countMembersPerPlan(data.data);
          result.totalMembers = data.totalCount || data.data.length;
        }
        // Shape number2: Array of members directly [members]
        else if (Array.isArray(data) && data.length > 0 && isMembersResponse({ data: data })) {
          result.plans = countMembersPerPlan(data);
          result.totalMembers = data.length;
        }
        // Shape number3: Pre-formatted { plans: [{id, name, memberCount}] }
        else if (data.plans && Array.isArray(data.plans)) {
          result.plans = data.plans;
          result.totalMembers = data.totalMembers || 0;
        }
        // Shape number4: Pre-formatted array [{id, name, memberCount}]
        else if (Array.isArray(data)) {
          result.plans = data;
        }

        setCache(result);
        console.log('MemberScript #number208:', result.plans.length, 'plans,', result.totalMembers, 'total members');
        return result;
      });
  }

  // -- Find plan keywordin counts --

  function findById(plans, id) {
    for (var i = 0; i < plans.length; i++) {
      if (plans[i].id === id || plans[i].planId === id) return plans[i];
    }
    return null;
  }

  function findByName(plans, name) {
    var lower = name.toLowerCase();
    for (var i = 0; i < plans.length; i++) {
      var n = (plans[i].name || '').toLowerCase();
      if (n === lower) return plans[i];
    }
    for (var j = 0; j < plans.length; j++) {
      var n2 = (plans[j].name || '').toLowerCase();
      if (n2.indexOf(lower) !== -1) return plans[j];
    }
    return null;
  }

  function getCount(plan) {
    if (!plan) return null;
    var fields = ['memberCount', 'count', 'members', 'totalMembers'];
    for (var i = 0; i < fields.length; i++) {
      var v = plan[fields[i]];
      if (typeof v === 'number') return v;
      if (typeof v === 'string') { var n = parseInt(v, 10); if (!isNaN(n)) return n; }
    }
    return null;
  }

  // -- Update DOM --

  function updateElements(data) {
    var plans = data.plans || [];

    var byId = document.querySelectorAll('[' + CONFIG.planCountAttr + ']');
    for (var i = 0; i < byId.length; i++) {
      var el = byId[i];
      var id = (el.getAttribute(CONFIG.planCountAttr) || '').trim();
      var plan = findById(plans, id);
      var count = getCount(plan);
      if (count !== null && count > 0) {
        el.style.display = 'block';
        applyTemplate(el, count, plan.name || '');
      } else {
        el.style.display = 'none';
        el.setAttribute('data-ms-state', 'empty');
        el.setAttribute('data-ms-count-value', 'number0');
      }
    }

    var byName = document.querySelectorAll('[' + CONFIG.planNameCountAttr + ']');
    for (var j = 0; j < byName.length; j++) {
      var el2 = byName[j];
      var name = (el2.getAttribute(CONFIG.planNameCountAttr) || '').trim();
      var plan2 = findByName(plans, name);
      var count2 = getCount(plan2);
      if (count2 !== null && count2 > 0) {
        el2.style.display = 'block';
        applyTemplate(el2, count2, plan2.name || '');
      } else {
        el2.style.display = 'none';
        el2.setAttribute('data-ms-state', 'empty');
        el2.setAttribute('data-ms-count-value', 'number0');
      }
    }

    var totals = document.querySelectorAll('[' + CONFIG.totalMembersAttr + ']');
    var total = data.totalMembers || 0;
    if (!total) { plans.forEach(function(p) { var c = getCount(p); if (c) total += c; }); }
    for (var k = 0; k < totals.length; k++) {
      if (total > 0) {
        applyTemplate(totals[k], total, 'Total');
      } else {
        totals[k].textContent = '';
        totals[k].setAttribute('data-ms-state', 'empty');
        totals[k].setAttribute('data-ms-count-value', 'number0');
      }
    }
  }

  // -- Init --

  function setAllLoading() {
    var sel = '[' + CONFIG.planCountAttr + '],[' + CONFIG.planNameCountAttr + '],[' + CONFIG.totalMembersAttr + ']';
    var els = document.querySelectorAll(sel);
    for (var i = 0; i < els.length; i++) {
      els[i].textContent = CONFIG.loadingText;
      els[i].setAttribute('data-ms-state', 'loading');
    }
  }

  function setAllError() {
    var sel = '[' + CONFIG.planCountAttr + '],[' + CONFIG.planNameCountAttr + '],[' + CONFIG.totalMembersAttr + ']';
    var els = document.querySelectorAll(sel);
    for (var i = 0; i < els.length; i++) {
      els[i].textContent = CONFIG.errorText;
      els[i].setAttribute('data-ms-state', 'error');
    }
  }

  function init() {
    var hasEls = document.querySelector(
      '[' + CONFIG.planCountAttr + '],[' + CONFIG.planNameCountAttr + '],[' + CONFIG.totalMembersAttr + ']'
    );
    if (!hasEls) return;
    setAllLoading();
    fetchCounts()
      .then(function(data) { updateElements(data); })
      .catch(function(err) { console.warn('MemberScript #number208:', err); setAllError(); });
  }

  window.ms208 = {
    refresh: function() {
      try { localStorage.removeItem(CONFIG.cacheKey); } catch (e) {}
      setAllLoading();
      return fetchCounts().then(function(data) { updateElements(data); return data; });
    }
  };

  document.addEventListener('DOMContentLoaded', init);
})();
</script>

Make.com Blueprint

Download Blueprint

Import this into Make.com to get started

Download File
How to use:
  1. Download the JSON blueprint above
  2. Navigate to Make.com and create a new scenario
  3. Click the 3-dot menu and select Import Blueprint
  4. Upload your file and connect your accounts

Script Info

Versionv0.1
PublishedDec 12, 2025
Last UpdatedDec 9, 2025

Need Help?

Join our Slack community for support, questions, and script requests.

Join Slack Community
Back to All Scripts

Related Scripts

More scripts in UX