How to create dynamic pricing page buttons that change based on user's Memberstack plan in Webflow? Answered

Hi again, I’m trying to figure this out my entire day.

I have 4 plans:

  • Free
  • Free Promo (which has access to everything)
  • Paid Single Classroom
  • Paid School Bundle - both paid have prices for monthly and yearly

What I’m trying to do is to show relevant button depending on plan you have, either upgrade, downgrade or manage. I’ve tried with Gated Content but it’s impossible or I have no clue how to set up the logic behind gated content and plans…

Please advise 🙂

Comments

7 comments

  • Comment author
    Memberstack Team
    • Edited
    • Official comment

    The script #187 is a perfect fit for your situation because it automatically highlights the member’s active plan directly in the pricing table. Instead of relying on gated content logic (which can get complicated with multiple plans and variants), this script gives clear visual indicators like “Current Plan” so users instantly know where they stand.

    That way, you can keep the upgrade, downgrade, and manage buttons consistent across all plans, while the script ensures the right plan is marked for each member. It simplifies the setup, reduces confusion, and makes the pricing table much more intuitive for your members.

  • Comment author
    Raquel Lopez

    Hi there!
    You can only apply one gated content setting per element in Memberstack. To show different content to logged-in and logged-out users, you’ll first need to wrap each section in its own container.

    • To show content only to logged-out visitors, use:
    •  data-ms-content="!members"
    • To show content only to logged-in members, use:
    •  data-ms-content="members"

    Example:

    <div data-ms-content="!members">
      <p>Welcome! Please log in to see member-only content.</p>
    </div>
    
    <div data-ms-content="members">
      <p>Hi there, member! Here's your exclusive content.</p>
    </div>
    

    Then, when you want to show content to members based on their specific plan, you will add the gated content id to each element inside the logged in members wrapper
    For example:

    • One button only visible to members on the “Pro” plan
    • Another button for the “Basic” plan

    Here’s how you can do that:

    1. Create gated content in Memberstack for each plan.
    2. Use the content ID (auto-generated by Memberstack) in your HTML like this:
    <div data-ms-content="content-id-for-pro-plan">
    <button>Upgrade to Pro Features</button>
    </div>

    <div data-ms-content="content-id-for-basic-plan">
    <button>Upgrade to Basic Features</button>
    </div>

    You can now mix and match different content blocks based on membership status and plan. If you want to be more specific you can create more gated content plans to group different plans and apply their id to its data-ms-content attribute

    0
  • Comment author
    Sebastian Kacperski

    thanks Raquel Lopez but I think this is more complex. My brain almost evaporated when I was trying to describe it…

    I’m working on setting up a dynamic pricing page in Webflow and need some guidance on the best approach without using custom JavaScript, relying solely on Memberstack attributes and gated content logic if possible.

    The Goal:
    On our pricing page, we have two main plans (“Single Classroom” and “School Bundle”), each with a Monthly and Yearly option. We use a toggle in Webflow to switch between displaying the Monthly and Yearly pricing cards.
    For each displayed pricing card, I want to show the relevant action buttons based on the currently logged-in member’s active plan:

    1. Manage: Show this button only on the card corresponding to the member’s exact current plan/price. Links to the customer portal (data-ms-action="profile").
    2. Upgrade: Show this button on cards representing a higher tier plan OR the yearly version of the member’s current monthly plan. Links via data-ms-price:update.
    3. Downgrade: Show this button on cards representing a lower tier plan OR the monthly version of the member’s current yearly plan. Links via data-ms-price:update.
    4. Sign Up / Choose Plan: Show this button on all cards for logged-out users. Links via data-ms-price:add.

    Is this even possible?

    Extra question: How does Memberstack/Stripe handles upgrades in the middle of the month or upgrading from one plan to another eg monthly to yearly?

    oh and I was trying Gemini 2.5 and some custom scripting but I was getting ERROR: Memberstack ($memberstack.onReady) not found after 5000ms...

    I’m wasted 

    <script>
    document.addEventListener('DOMContentLoaded', function() {
    
        console.log("INFO: Validation script loaded. Starting poll for Memberstack...");
    
        // --- Configuration - Using Your Correct IDs ---
        const PLAN_ID_FREE = "pln_free-plan-dhnb0ejd";
        const PLAN_ID_PROMO = "pln_educators-free-promo-ebfw0xzj";
        const PLAN_ID_PAID = "pln_educators-p04704og";
        const PRICE_ID_SINGLE = "prc_single-classroom-gf9z0pxj";
        const PRICE_ID_BUNDLE = "prc_school-bundle-u3a00pu8";
        // --- End Configuration ---
    
        // --- Polling Configuration ---
        const pollInterval = 100; // Check every 100ms
        const pollTimeout = 5000; // Stop polling after 5 seconds
    
        let intervalId = null;
        let timeoutId = null;
    
        // --- Function containing the core Memberstack logic ---
        function runMemberstackCheck() {
            console.log("INFO: Attaching Memberstack onReady listener...");
    
            $memberstack.onReady.then(function(member) {
                console.log("INFO: Memberstack is Ready."); // <<< EXPECT TO SEE THIS
    
                console.log("DEBUG: Raw member object received:", JSON.parse(JSON.stringify(member || null)));
    
                if (member) {
                    console.log(`INFO: User is logged in. Member ID: ${member.id}`);
                    if (member.planConnections && Array.isArray(member.planConnections)) {
                        console.log("INFO: 'planConnections' array found.");
                        const activeConnections = member.planConnections.filter(pc => pc.status === 'ACTIVE');
                        if (activeConnections.length > 0) {
                            console.log(`INFO: Found ${activeConnections.length} active plan connection(s).`);
                            activeConnections.forEach((connection, index) => {
                                console.log(`------------------------------------------`);
                                console.log(`INFO: Details for Active Connection #${index + 1}:`);
                                console.log(`  -> planId: ${connection.planId || 'Not Set'}`);
                                console.log(`  -> priceId: ${connection.priceId || 'Not Set'}`);
                                console.log(`  -> status: ${connection.status}`);
                                let planMatchType = "Unknown/Other Plan";
                                if (connection.planId === PLAN_ID_FREE) planMatchType = "MATCH: Educators Free";
                                else if (connection.planId === PLAN_ID_PROMO) planMatchType = "MATCH: Educators Free Promo";
                                else if (connection.planId === PLAN_ID_PAID) {
                                    planMatchType = "MATCH: Educators Paid";
                                    if (connection.priceId === PRICE_ID_SINGLE) planMatchType += " (Price: Single Classroom)";
                                    else if (connection.priceId === PRICE_ID_BUNDLE) planMatchType += " (Price: School Bundle)";
                                    else planMatchType += ` (Price: UNKNOWN/MISMATCH - Found Price ID '${connection.priceId || 'None'}')`;
                                } else if (connection.planId) planMatchType = `MISMATCH: Found active Plan ID '${connection.planId}' not in config.`;
                                console.log(`  -> VALIDATION: ${planMatchType}`);
                                console.log(`------------------------------------------`);
                            });
                        } else {
                            console.warn("WARN: User logged in, but no active plan connections found.");
                        }
                    } else {
                        console.error("ERROR: User logged in, but 'planConnections' array missing/invalid.");
                    }
                } else {
                    console.info("INFO: User is not logged in (member object is null/undefined in onReady).");
                }
            }).catch(error => {
                console.error("ERROR: An error occurred during $memberstack.onReady execution:", error);
            });
        }
    
    
        // --- Start Polling ---
        intervalId = setInterval(function() {
            // Check if $memberstack and the .onReady method are available
            if (typeof $memberstack !== 'undefined' && typeof $memberstack.onReady !== 'undefined') {
                console.log(`INFO: Memberstack ($memberstack.onReady) found after polling.`);
                // Stop polling
                clearInterval(intervalId);
                clearTimeout(timeoutId); // Cancel the timeout watchdog
                // Run the main logic
                runMemberstackCheck();
            } else {
                 // Optional: Log dots to show polling is active
                 // console.log(".");
            }
        }, pollInterval);
    
        // --- Set a Timeout for Polling ---
        timeoutId = setTimeout(function() {
            clearInterval(intervalId); // Stop polling
            console.error(`ERROR: Memberstack ($memberstack.onReady) not found after ${pollTimeout}ms. Stopping poll.`);
            // Optionally, you could try to run runMemberstackCheck() anyway,
            // but it would likely fail if .onReady wasn't found by now.
        }, pollTimeout);
    
    });
    </script>
    
    0
  • Comment author
    A J

    Hey Sebastian Kacperski, for your requirements 1-3, you could make use of custom code, since as of now, there are no direct attributes that could help out in price level based customization.

    I found this code by Chukwudi somewhere which could act as a base code for you where you can update your price IDs and plan IDs and try showing the relevant buttons on the pricing page for logged in users.

    <script>
    document.addEventListener("DOMContentLoaded", function () {
        const buttonPriceA = document.querySelector('#current-monthly');
        const buttonPriceB = document.querySelector('#current-annual');
        const buttonPlanA = document.querySelector('#buttonA');
        const buttonPlanB = document.querySelector('#buttonB');
    
        // Hide buttons initially
        buttonPriceA.style.display = 'none';
        buttonPriceB.style.display = 'none';
        buttonPlanA.style.display = 'none';
        buttonPlanB.style.display = 'none';
    
        window.$memberstackDom.getCurrentMember().then((member) => {
            if (member.data) {
                const planConnections = member.data["planConnections"];
                if (planConnections && planConnections.length > 0) {
                    const priceA = "prc_IDForPriceA"; // Replace with the Price ID of Price A
                    const priceB = "prc_IDForPriceB"; // Replace with the Price ID of Price B
                    const planA = "pln_IDForPlanA"; // Replace with the Plan ID of Plan A
                    const planB = "pln_IDForPlanB"; // Replace with the Plan ID of Plan B
    
                    // Filter out planConnections where payment or planId is null or undefined
                    const priceIds = planConnections
                        .filter(connection => connection.payment) // Ensure payment exists
                        .map(connection => connection.payment.priceId);
    
                    const planIds = planConnections
                        .filter(connection => connection.planId) // Ensure planId exists
                        .map(connection => connection.planId);
    
                    // Check for price IDs
                    if (priceIds.includes(priceA)) { buttonPriceA.style.display = 'block'; }
                    if (priceIds.includes(priceB)) { buttonPriceB.style.display = 'block'; }
    
                    // Check for plan IDs
                    if (planIds.includes(planA)) { buttonPlanA.style.display = 'block'; }
                    if (planIds.includes(planB)) { buttonPlanB.style.display = 'block'; }
    
                    // Log if no active plan or price is found
                    if (!priceIds.some(id => [priceA, priceB].includes(id)) &&
                        !planIds.some(id => [planA, planB].includes(id))) {
                        console.log('No active plan or price');
                    }
                }
            }
        }).catch(() => {
            console.error('Failed to load Memberstack data.');
        });
    });
    </script> 
    

    Make sure you replace the price, plan and button IDs accordingly in the code and customize it for your project.

    And as far as 4th requirement is concerned for Signup / Choose plan buttons, you could just add the custom attribute data-ms-content="!members" to such buttons as stated by Raquel above and such buttons will only be shown for logged out visitors.

    Make sure you wrap the other buttons or the div component with the attribute data-ms-content="members" just in case to ensure it only loads for logged in members and you can apply the custom code logic to then decide what should be visible based on the logged in user's plan. Hope this helps.

    0
  • Comment author
    Raquel Lopez

    If your scenario is complex, it will most likely require some custom code to handle everything properly. Memberstack data attributes generally cover the most common membership flows. For specifics you'll have to dirty your hands with code.

    That said, I still believe the solution I shared earlier could work for your situation. It doesn't add any custom code but it just might feel a bit cumbersome because you'd need to create multiple gated content blocks tailored to each plan or condition.

    My recommendation: Instead of creating separate buttons (e.g. one for upgrade, another for downgrade), you can simplify your logic by using a single button that takes users to a unified subscription management page. From there, they can choose to upgrade, downgrade. It's the most common approach for pricing tables, you can get some examples in Memberstack components library.

    This way:

    • You reduce the number of gated elements to manage.
    • Your logic stays clean and easier to maintain.
    • The user experience stays consistent.

    If you still want to try custom code yourself, I will recommend use Rey, Memberstack AI bot. The answers are better when using Memberstack library. Other AIs don't have much context. Also for a risk free and maintainable solution you can always hire one of the Memberstack experts.

    Extra question: How does Memberstack/Stripe handles upgrades in the middle of the month or upgrading from one plan to another eg monthly to yearly?
    They automatically calculate the surplus or difference at checkout. So the user gets to see the remaining balance there along with the new pricing.
    0
  • Comment author
    Sebastian Kacperski

    Thanks everyone! I’ve managed to solve it with custom code and A J your code actually unblocked me to work with AI on what I need.

    I’ll sum up this later today but as for now I’m struggling with a simple (hopefully) setup on the Stripe Billing Poratl.

    Why I don’t see all my plans? I have 4:

    1. Educators Single Classroom Monthly - $19
    2. Educators Single Classroom Yearly - $159
    3. Educators School Bundle Monthly - $59
    4. Educators School Bundle Yearly - $495

    Yet when I:

    1. Click to downgrade on a monthly one I only see monthly subscriptions
    2. Click to upgrade on a yearly one I see monthly and yearly
    3. Do I always have click on the “Manage Subscription”? Can I initiate the purchase before?

    My ideal scenario is when someone want to downgrade / upgrade we are launching the purchase flow not the billing portal. If someone once to manage their subscription they open Billing portal (this works actually).

    Second ideal scenario is that when someone want to downgrade / upgrade he is taken to Biling portal and see all the options.

    Thanks everyone, you’re awesome.

    https://memberstack.slack.com/files/U02VA9M30H4/F08MJ544WR5/upgrade-pwofsmgo.mp4

    0
  • Comment author
    Raquel Lopez

    I see.

    It's possible because you are subscribed to a monthly fee, you only get to see monthly plans, except when you click upgrade to a yearly plan, that you see your current monthly plan, and the yearly plan you selected when clicking upgrade.

    If you're open to custom code now, you can control what plans are shown in the customer billing portal when you click a button.You can use this method

    document.querySelector('#yourButton').addEventListener('click', (ev) =>{
    ev.preventDefault();
    window.$memberstackDom.launchStripeCustomerPortal({
            priceIds: ['monthlyPlanId1', 'monthlPlanId2', 'yearlyPlanId1', 'yearlyPlanId2'],
          })
    })
    

    It should open the customer billing portal with all the plans (by adding all your current price ids from your store). Also you could modify the script to only show the specific plans that you want

    0

Please sign in to leave a comment.