Components
Templates
Attributes
Integrations
Site Tester
Custom Code

MemberScripts

An attribute-based solution to add features to your Webflow site.
Simply copy some code, add some attributes, and you're done.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Need help with MemberScripts?

All Memberstack customers can ask for assistance in the 2.0 Slack. Please note that these are not official features and support cannot be guaranteed.

Custom Flows

#87 - Remove a Plan After Countdown

Create time-sensitive secure content!


<!-- 💙 MEMBERSCRIPT #87 v0.1 💙 REMOVE PLAN AFTER COUNTDOWN -->
<script>
const memberstack = window.$memberstackDom;
const countdown = new Date(localStorage.getItem('countdownDateTime'));

// Check if date has passed
const checkDate = async () => {
  const now = new Date();
  if (now > countdown) {
    // Remove member's free plan
    await memberstack.removePlan({
      planId: "pln_10-minutes-of-gif-access-rw1fh0ktg"
    });
    console.log("Plan removed");

    // Reload the page
    location.reload();
  }
}

// Execute checkDate every 10s
const intervalId = setInterval(checkDate, 10000);
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #87 v0.1 💙 REMOVE PLAN AFTER COUNTDOWN -->
<script>
const memberstack = window.$memberstackDom;
const countdown = new Date(localStorage.getItem('countdownDateTime'));

// Check if date has passed
const checkDate = async () => {
  const now = new Date();
  if (now > countdown) {
    // Remove member's free plan
    await memberstack.removePlan({
      planId: "pln_10-minutes-of-gif-access-rw1fh0ktg"
    });
    console.log("Plan removed");

    // Reload the page
    location.reload();
  }
}

// Execute checkDate every 10s
const intervalId = setInterval(checkDate, 10000);
</script>
View Memberscript
UX

#86 - Free & Simple Text-To-Speech

Add a button which allows visitors to listen to your article.


<!-- 💙 MEMBERSCRIPT #86 v0.1 💙 VOICE TO TEXT BUTTON -->
<script>
document.addEventListener('DOMContentLoaded', (event) => {
    const textDiv = document.querySelector('[ms-code-text-to-speech="text"]');
    const speakButton = document.querySelector('[ms-code-text-to-speech="button"]');
    let utterance = new SpeechSynthesisUtterance();

    speakButton.addEventListener('click', () => {
        if(speechSynthesis.speaking || speechSynthesis.paused) {
            speechSynthesis.cancel(); // stops current speech
        } else {
            utterance.text = textDiv.innerText;

            speechSynthesis.speak(utterance); // starts speaking
        }
    });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #86 v0.1 💙 VOICE TO TEXT BUTTON -->
<script>
document.addEventListener('DOMContentLoaded', (event) => {
    const textDiv = document.querySelector('[ms-code-text-to-speech="text"]');
    const speakButton = document.querySelector('[ms-code-text-to-speech="button"]');
    let utterance = new SpeechSynthesisUtterance();

    speakButton.addEventListener('click', () => {
        if(speechSynthesis.speaking || speechSynthesis.paused) {
            speechSynthesis.cancel(); // stops current speech
        } else {
            utterance.text = textDiv.innerText;

            speechSynthesis.speak(utterance); // starts speaking
        }
    });
});
</script>
View Memberscript
Custom Fields

#85 - "Add a row" Form Inputs

Allow members to add and delete rows from a form input.


<!-- 💙 MEMBERSCRIPT #85 v0.1 💙 ADD A ROW FORM INPUTS -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
  $(document).ready(function() {
    // Hide all rows except the original row
    $('[ms-code-row-input="new"]').hide();

    // Add row button click event
    $('[ms-code-row-input="add-row"]').click(function(e) {
      e.preventDefault();
      var clonedRow = $('[ms-code-row-input="new"]').first().clone();
      clonedRow.find('input').val('');
      clonedRow.show().appendTo('[ms-code-row-input="row-container"]');

      updateHolderValue();
    });

    // Delete row button click event
    $(document).on('click', '[ms-code-row-input="delete"]', function(e) {
      e.preventDefault();
      $(this).closest('[ms-code-row-input="new"]').remove();

      updateHolderValue();
    });

    // Event for all inputs
    $(document).on('input', '[ms-code-row-input="original"], [ms-code-row-input="new-input"], [ms-code-row-input="holder"]', function() {
      if ($(this).is('[ms-code-row-input="holder"]')) {
        updateRowsFromHolder();
      } else {
        updateHolderValue();
      }
    });

    // Function to update the holder input value
    function updateHolderValue() {
      var values = [];
      $('[ms-code-row-input="original"], [ms-code-row-input="new-input"]').each(function() {
        var value = $(this).val().trim();
        if (value) {
          values.push(value);
        }
      });
      $('[ms-code-row-input="holder"]').val(values.join(','));
    }

    // Function to update rows from the holder field
    function updateRowsFromHolder() {
      var holderValue = $('[ms-code-row-input="holder"]').val();
      var values = holderValue.split(',');

      $('[ms-code-row-input="new"]').not(':first').remove();

      // For each holder value, create a new row
      values.forEach(function(val, idx) {
        if (idx === 0) {
          $('[ms-code-row-input="original"]').val(val);
        } else {
          var newRow = $('[ms-code-row-input="new"]').first().clone().appendTo('[ms-code-row-input="row-container"]');
          newRow.find('input').val(val);
          newRow.show();
        }
      });
    }

    // Initial update of the holder input value
    updateHolderValue();

    // Adding MutationObserver to call updateRowsFromHolder on changes to the holder field
    var targetNode = $('[ms-code-row-input="holder"]')[0];
    var config = { attributes: true, childList: true, subtree: true };
    var callback = function(mutationsList, observer) {
      for(let mutation of mutationsList) {
        if (mutation.type === 'childList')
        {
          updateRowsFromHolder();
        }
      }
    };
    var observer = new MutationObserver(callback);
    observer.observe(targetNode, config);
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #85 v0.1 💙 ADD A ROW FORM INPUTS -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
  $(document).ready(function() {
    // Hide all rows except the original row
    $('[ms-code-row-input="new"]').hide();

    // Add row button click event
    $('[ms-code-row-input="add-row"]').click(function(e) {
      e.preventDefault();
      var clonedRow = $('[ms-code-row-input="new"]').first().clone();
      clonedRow.find('input').val('');
      clonedRow.show().appendTo('[ms-code-row-input="row-container"]');

      updateHolderValue();
    });

    // Delete row button click event
    $(document).on('click', '[ms-code-row-input="delete"]', function(e) {
      e.preventDefault();
      $(this).closest('[ms-code-row-input="new"]').remove();

      updateHolderValue();
    });

    // Event for all inputs
    $(document).on('input', '[ms-code-row-input="original"], [ms-code-row-input="new-input"], [ms-code-row-input="holder"]', function() {
      if ($(this).is('[ms-code-row-input="holder"]')) {
        updateRowsFromHolder();
      } else {
        updateHolderValue();
      }
    });

    // Function to update the holder input value
    function updateHolderValue() {
      var values = [];
      $('[ms-code-row-input="original"], [ms-code-row-input="new-input"]').each(function() {
        var value = $(this).val().trim();
        if (value) {
          values.push(value);
        }
      });
      $('[ms-code-row-input="holder"]').val(values.join(','));
    }

    // Function to update rows from the holder field
    function updateRowsFromHolder() {
      var holderValue = $('[ms-code-row-input="holder"]').val();
      var values = holderValue.split(',');

      $('[ms-code-row-input="new"]').not(':first').remove();

      // For each holder value, create a new row
      values.forEach(function(val, idx) {
        if (idx === 0) {
          $('[ms-code-row-input="original"]').val(val);
        } else {
          var newRow = $('[ms-code-row-input="new"]').first().clone().appendTo('[ms-code-row-input="row-container"]');
          newRow.find('input').val(val);
          newRow.show();
        }
      });
    }

    // Initial update of the holder input value
    updateHolderValue();

    // Adding MutationObserver to call updateRowsFromHolder on changes to the holder field
    var targetNode = $('[ms-code-row-input="holder"]')[0];
    var config = { attributes: true, childList: true, subtree: true };
    var callback = function(mutationsList, observer) {
      for(let mutation of mutationsList) {
        if (mutation.type === 'childList')
        {
          updateRowsFromHolder();
        }
      }
    };
    var observer = new MutationObserver(callback);
    observer.observe(targetNode, config);
  });
</script>
View Memberscript
UX
Custom Fields

#84 - Clear Inputs OnLoad

Add this script to any page to clear the value of a custom field on page load.


<!-- 💙 MEMBERSCRIPT #84 v0.1 💙 CLEAR INPUT VALUES ONLOAD -->
<script>
  document.addEventListener('DOMContentLoaded', async function() {
    const memberstack = window.$memberstackDom;
    const fieldsToClear = ["phone", "last-name"]; // Specify the fields to clear

    // Clear inputs and Memberstack fields on page load
    memberstack.getCurrentMember().then(async ({ data: member }) => {
      if (member) {
        const customFieldsToUpdate = {};
      
        fieldsToClear.forEach(fieldName => {
          customFieldsToUpdate[fieldName] = '';
        });

        try {
          await memberstack.updateMember({
            customFields: customFieldsToUpdate
          });
          console.log("Fields cleared on page load.");
        } catch (error) {
          console.error('Error clearing fields on page load:', error);
        }
      }
      
      // Clear input values on page load for specified fields
      fieldsToClear.forEach(fieldName => {
        const inputField = document.querySelector(`[data-ms-member="${fieldName}"]`);
        if (inputField) {
          inputField.value = '';
        }
      });
    });
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #84 v0.1 💙 CLEAR INPUT VALUES ONLOAD -->
<script>
  document.addEventListener('DOMContentLoaded', async function() {
    const memberstack = window.$memberstackDom;
    const fieldsToClear = ["phone", "last-name"]; // Specify the fields to clear

    // Clear inputs and Memberstack fields on page load
    memberstack.getCurrentMember().then(async ({ data: member }) => {
      if (member) {
        const customFieldsToUpdate = {};
      
        fieldsToClear.forEach(fieldName => {
          customFieldsToUpdate[fieldName] = '';
        });

        try {
          await memberstack.updateMember({
            customFields: customFieldsToUpdate
          });
          console.log("Fields cleared on page load.");
        } catch (error) {
          console.error('Error clearing fields on page load:', error);
        }
      }
      
      // Clear input values on page load for specified fields
      fieldsToClear.forEach(fieldName => {
        const inputField = document.querySelector(`[data-ms-member="${fieldName}"]`);
        if (inputField) {
          inputField.value = '';
        }
      });
    });
  });
</script>
View Memberscript
UX

#83 - Cross-Device Cookie Preferences

Allow members to save their cookie preferences to their account.


<!-- 💙 MEMBERSCRIPT #83 v0.1 💙 CROSS-DEVICE COOKIE PREFERENCES -->
<script>
// Function to retrieve a cookie value by name
function getCookie(name) {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; ${name}=`);
  if (parts.length === 2) return decodeURIComponent(parts.pop().split(';').shift());
}

async function updateMemberConsentPreferences(fsCcCookieValue) {
  try {
    const memberstack = window.$memberstackDom;
    const userData = await memberstack.getCurrentMember();

    if (userData && userData.data.customFields) {
      if (!userData.data.customFields['cookie-consent']) {
        const decodedFsCcCookieValue = decodeURIComponent(fsCcCookieValue);
        await memberstack.updateMember({
          customFields: {
            'cookie-consent': decodedFsCcCookieValue
          }
        });
      } else {
        document.cookie = `fs-cc=${encodeURIComponent(userData.data.customFields['cookie-consent'])}`;
      }
    }
  } catch (error) {}
}

async function initialize() {
  const fsCcCookieValue = getCookie('fs-cc');
  if (fsCcCookieValue) {
    await updateMemberConsentPreferences(fsCcCookieValue);

    const checkboxes = document.querySelectorAll('[fs-cc-checkbox]');
    checkboxes.forEach(checkbox => {
      checkbox.addEventListener('change', async () => {
        const memberstack = window.$memberstackDom;
        const userData = await memberstack.getCurrentMember();
        
        if (userData && userData.data.customFields) {
          const customFieldKey = 'cookie-consent';
          const checkboxName = checkbox.getAttribute('fs-cc-checkbox');

          if (userData.data.customFields[customFieldKey]) {
            const consentData = JSON.parse(userData.data.customFields[customFieldKey]);
            consentData.consents[checkboxName] = checkbox.checked;
            const updatedCustomField = JSON.stringify(consentData);

            await memberstack.updateMember({
              customFields: {
                [customFieldKey]: updatedCustomField
              }
            });

            document.cookie = `fs-cc=${encodeURIComponent(updatedCustomField)}`;
          }
        }
      });
    });
  }
}

// Initialize the script
initialize();
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #83 v0.1 💙 CROSS-DEVICE COOKIE PREFERENCES -->
<script>
// Function to retrieve a cookie value by name
function getCookie(name) {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; ${name}=`);
  if (parts.length === 2) return decodeURIComponent(parts.pop().split(';').shift());
}

async function updateMemberConsentPreferences(fsCcCookieValue) {
  try {
    const memberstack = window.$memberstackDom;
    const userData = await memberstack.getCurrentMember();

    if (userData && userData.data.customFields) {
      if (!userData.data.customFields['cookie-consent']) {
        const decodedFsCcCookieValue = decodeURIComponent(fsCcCookieValue);
        await memberstack.updateMember({
          customFields: {
            'cookie-consent': decodedFsCcCookieValue
          }
        });
      } else {
        document.cookie = `fs-cc=${encodeURIComponent(userData.data.customFields['cookie-consent'])}`;
      }
    }
  } catch (error) {}
}

async function initialize() {
  const fsCcCookieValue = getCookie('fs-cc');
  if (fsCcCookieValue) {
    await updateMemberConsentPreferences(fsCcCookieValue);

    const checkboxes = document.querySelectorAll('[fs-cc-checkbox]');
    checkboxes.forEach(checkbox => {
      checkbox.addEventListener('change', async () => {
        const memberstack = window.$memberstackDom;
        const userData = await memberstack.getCurrentMember();
        
        if (userData && userData.data.customFields) {
          const customFieldKey = 'cookie-consent';
          const checkboxName = checkbox.getAttribute('fs-cc-checkbox');

          if (userData.data.customFields[customFieldKey]) {
            const consentData = JSON.parse(userData.data.customFields[customFieldKey]);
            consentData.consents[checkboxName] = checkbox.checked;
            const updatedCustomField = JSON.stringify(consentData);

            await memberstack.updateMember({
              customFields: {
                [customFieldKey]: updatedCustomField
              }
            });

            document.cookie = `fs-cc=${encodeURIComponent(updatedCustomField)}`;
          }
        }
      });
    });
  }
}

// Initialize the script
initialize();
</script>
View Memberscript
Custom Flows

#82 - License Keys

Secure your downloadable content with license keys.


<!-- 💙 MEMBERSCRIPT #82 v0.1 💙 LICENSE KEYS -->
<script>
const memberstack = window.$memberstackDom;

// Initialize MutationObserver
const observer = new MutationObserver(async (mutations) => {
  const downloadBtn = document.getElementById("download");
  
  if (downloadBtn) {
    // Element exists, so add event listener
    downloadBtn.addEventListener("click", async () => {
      await memberstack.removePlan({
        planId: "pln_activate-license-key-952c0d8u"
      });
      console.log("Plan removed");
    });

    // Stop observing since we found the element
    observer.disconnect();
  }
});

// Observe the whole document
observer.observe(document.body, { childList: true, subtree: true });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #82 v0.1 💙 LICENSE KEYS -->
<script>
const memberstack = window.$memberstackDom;

// Initialize MutationObserver
const observer = new MutationObserver(async (mutations) => {
  const downloadBtn = document.getElementById("download");
  
  if (downloadBtn) {
    // Element exists, so add event listener
    downloadBtn.addEventListener("click", async () => {
      await memberstack.removePlan({
        planId: "pln_activate-license-key-952c0d8u"
      });
      console.log("Plan removed");
    });

    // Stop observing since we found the element
    observer.disconnect();
  }
});

// Observe the whole document
observer.observe(document.body, { childList: true, subtree: true });
</script>
View Memberscript
Custom Fields

#81 - Custom Checkbox Values

Pass through a unique value based on whether or not the box is checked.


<!-- 💙 MEMBERSCRIPT #81 v0.1 💙 CUSTOM CHECKBOX VALUES -->
<script>
document.addEventListener('submit', function(e) {
  var checkboxes = document.querySelectorAll('[ms-code-custom-checkbox]');
  
  checkboxes.forEach(function(checkbox) {
    var values = checkbox.getAttribute('ms-code-custom-checkbox').split(',');
    var valueToSubmit = checkbox.checked ? values[0] : values[1];

    var hiddenInput = document.createElement('input');
    
    // Copy all attributes except type and ms-code-custom-checkbox
    for (var i = 0; i < checkbox.attributes.length; i++) {
      var attr = checkbox.attributes[i];
      if (attr.name !== 'type' && attr.name !== 'ms-code-custom-checkbox') {
        hiddenInput.setAttribute(attr.name, attr.value);
      }
    }
    
    hiddenInput.type = 'hidden';
    hiddenInput.value = valueToSubmit;
    
    checkbox.form.appendChild(hiddenInput);
    checkbox.remove(); // Remove the original checkbox so it doesn't interfere with submission
  });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #81 v0.1 💙 CUSTOM CHECKBOX VALUES -->
<script>
document.addEventListener('submit', function(e) {
  var checkboxes = document.querySelectorAll('[ms-code-custom-checkbox]');
  
  checkboxes.forEach(function(checkbox) {
    var values = checkbox.getAttribute('ms-code-custom-checkbox').split(',');
    var valueToSubmit = checkbox.checked ? values[0] : values[1];

    var hiddenInput = document.createElement('input');
    
    // Copy all attributes except type and ms-code-custom-checkbox
    for (var i = 0; i < checkbox.attributes.length; i++) {
      var attr = checkbox.attributes[i];
      if (attr.name !== 'type' && attr.name !== 'ms-code-custom-checkbox') {
        hiddenInput.setAttribute(attr.name, attr.value);
      }
    }
    
    hiddenInput.type = 'hidden';
    hiddenInput.value = valueToSubmit;
    
    checkbox.form.appendChild(hiddenInput);
    checkbox.remove(); // Remove the original checkbox so it doesn't interfere with submission
  });
});
</script>
View Memberscript
UX
Marketing

#80 - Plan Cancelled Notification

Trigger a Slack notification when a member cancels their plan.

v0.1
View Memberscript
UX

#79 - Trigger Click onHover

Trigger a click event onHover.


<!-- 💙 MEMBERSCRIPT #79 v0.1 💙 HOVER BASED TABS -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    const hoverTabElements = document.querySelectorAll('[ms-code-onhover="click"]');

    hoverTabElements.forEach(hoverTabElement => {
      hoverTabElement.addEventListener('mouseenter', function() {
        hoverTabElement.click(); // Click on the element when hovering
      });
    });
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #79 v0.1 💙 HOVER BASED TABS -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    const hoverTabElements = document.querySelectorAll('[ms-code-onhover="click"]');

    hoverTabElements.forEach(hoverTabElement => {
      hoverTabElement.addEventListener('mouseenter', function() {
        hoverTabElement.click(); // Click on the element when hovering
      });
    });
  });
</script>
View Memberscript
UX

#78 - Clear Inputs OnClick

Create a button which can clear the values of one or more inputs.


<!-- 💙 MEMBERSCRIPT #78 v0.1 💙 CLEAR INPUT VALUES ONCLICK -->
<script>
document.addEventListener('DOMContentLoaded', () => {
  const clearBtns = document.querySelectorAll('[ms-code-clear-value]');
  clearBtns.forEach(btn => {
    btn.addEventListener('click', () => {
      const fieldIds = btn.getAttribute('ms-code-clear-value').split(',');
      fieldIds.forEach(fieldId => {   
        const input = document.querySelector(`[data-ms-member="${fieldId}"]`);
        if (input) {
          input.value = '';
        }
      });
    });
  });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #78 v0.1 💙 CLEAR INPUT VALUES ONCLICK -->
<script>
document.addEventListener('DOMContentLoaded', () => {
  const clearBtns = document.querySelectorAll('[ms-code-clear-value]');
  clearBtns.forEach(btn => {
    btn.addEventListener('click', () => {
      const fieldIds = btn.getAttribute('ms-code-clear-value').split(',');
      fieldIds.forEach(fieldId => {   
        const input = document.querySelector(`[data-ms-member="${fieldId}"]`);
        if (input) {
          input.value = '';
        }
      });
    });
  });
});
</script>
View Memberscript
UX

#77 - Universal Emojis

Make your on-site emojis the same on all devices/OS.


<!-- 💙 MEMBERSCRIPT #77 v0.1 💙 UNIVERSAL EMOJIS -->
<script>
document.querySelectorAll('[ms-code-emoji]').forEach(element => {
  var imageUrl = element.getAttribute('ms-code-emoji');
  var img = document.createElement('img');
  img.src = imageUrl;

  var textStyle = window.getComputedStyle(element);
  var adjustedHeight = parseFloat(textStyle.fontSize) * 1.0;

  img.style.height = adjustedHeight + 'px';
  img.style.width = 'auto';
  img.style.verticalAlign = 'text-top';

  element.innerHTML = ''; // Clears the text content inside the span
  element.appendChild(img);
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #77 v0.1 💙 UNIVERSAL EMOJIS -->
<script>
document.querySelectorAll('[ms-code-emoji]').forEach(element => {
  var imageUrl = element.getAttribute('ms-code-emoji');
  var img = document.createElement('img');
  img.src = imageUrl;

  var textStyle = window.getComputedStyle(element);
  var adjustedHeight = parseFloat(textStyle.fontSize) * 1.0;

  img.style.height = adjustedHeight + 'px';
  img.style.width = 'auto';
  img.style.verticalAlign = 'text-top';

  element.innerHTML = ''; // Clears the text content inside the span
  element.appendChild(img);
});
</script>
View Memberscript
Conditional Visibility
UX

#76 - Time-Based Visibility

Show different elements based on the current time of day.


<!-- 💙 MEMBERSCRIPT #76 v0.1 💙 TIME-BASED VISIBILITY -->
<script>
function hideElements() {
  const elements = document.querySelectorAll('[ms-code-time]');
  elements.forEach(element => {
    element.style.display = 'none';
  });
}

function displayBasedOnTime() {
  const elements = document.querySelectorAll('[ms-code-time]');
  const currentTime = new Date();

  elements.forEach(element => {
    const timeRange = element.getAttribute('ms-code-time');
    const [start, end] = timeRange.split(' - ');
    const [startHour, startMinute] = start.split(':').map(Number);
    const [endHour, endMinute] = end.split(':').map(Number);

    let startTime = new Date(currentTime);
    startTime.setHours(startHour, startMinute, 0, 0);

    let endTime = new Date(currentTime);
    endTime.setHours(endHour, endMinute, 0, 0);

    // If the end time is earlier than the start time, add a day to the end time
    if (endTime < startTime) {
      endTime.setDate(endTime.getDate() + 1);
    }

    if (currentTime >= startTime && currentTime <= endTime) {
      element.style.display = 'flex';
    }
  });
}

// Call the functions
hideElements();
displayBasedOnTime();
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #76 v0.1 💙 TIME-BASED VISIBILITY -->
<script>
function hideElements() {
  const elements = document.querySelectorAll('[ms-code-time]');
  elements.forEach(element => {
    element.style.display = 'none';
  });
}

function displayBasedOnTime() {
  const elements = document.querySelectorAll('[ms-code-time]');
  const currentTime = new Date();

  elements.forEach(element => {
    const timeRange = element.getAttribute('ms-code-time');
    const [start, end] = timeRange.split(' - ');
    const [startHour, startMinute] = start.split(':').map(Number);
    const [endHour, endMinute] = end.split(':').map(Number);

    let startTime = new Date(currentTime);
    startTime.setHours(startHour, startMinute, 0, 0);

    let endTime = new Date(currentTime);
    endTime.setHours(endHour, endMinute, 0, 0);

    // If the end time is earlier than the start time, add a day to the end time
    if (endTime < startTime) {
      endTime.setDate(endTime.getDate() + 1);
    }

    if (currentTime >= startTime && currentTime <= endTime) {
      element.style.display = 'flex';
    }
  });
}

// Call the functions
hideElements();
displayBasedOnTime();
</script>
View Memberscript
UX
Custom Fields

#75 - Disallowed Character Inputs

Show a custom error message if a user enters something you set in an input.


<!-- 💙 MEMBERSCRIPT #75 v0.1 💙 DISALOWED CHARACTER INPUTS -->
<script>
document.addEventListener('DOMContentLoaded', function() {
  const inputFields = document.querySelectorAll('[ms-code-disallow]');
  inputFields.forEach(inputField => {
    const errorBlock = inputField.nextElementSibling;
    errorBlock.innerHTML = ''; // Use innerHTML to interpret <br> tags

    inputField.addEventListener('input', function() {
      const rules = inputField.getAttribute('ms-code-disallow').split(')');
      let errorMessage = '';

      rules.forEach(rule => {
        const parts = rule.trim().split('=');
        const ruleType = parts[0].substring(1); // Remove the opening parenthesis
        const disallowedValue = parts[1];

        if (ruleType.startsWith('custom')) {
          const disallowedChar = ruleType.split('-')[1]; // Extract the character after the '-'
          if (inputField.value.includes(disallowedChar)) {
            errorMessage += disallowedValue + '<br>'; // Add line break
          }
        } else if (ruleType === 'space' && inputField.value.includes(' ')) {
          errorMessage += disallowedValue + '<br>'; // Add line break
        } else if (ruleType === 'number' && /\d/.test(inputField.value)) {
          errorMessage += disallowedValue + '<br>'; // Add line break
        } else if (ruleType === 'special' && /[^a-zA-Z0-9\s]/.test(inputField.value)) { // Notice the \s here
          errorMessage += disallowedValue + '<br>'; // Add line break
        }
      });

      errorBlock.innerHTML = errorMessage || ''; // Use innerHTML to interpret <br> tags
    });
  });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #75 v0.1 💙 DISALOWED CHARACTER INPUTS -->
<script>
document.addEventListener('DOMContentLoaded', function() {
  const inputFields = document.querySelectorAll('[ms-code-disallow]');
  inputFields.forEach(inputField => {
    const errorBlock = inputField.nextElementSibling;
    errorBlock.innerHTML = ''; // Use innerHTML to interpret <br> tags

    inputField.addEventListener('input', function() {
      const rules = inputField.getAttribute('ms-code-disallow').split(')');
      let errorMessage = '';

      rules.forEach(rule => {
        const parts = rule.trim().split('=');
        const ruleType = parts[0].substring(1); // Remove the opening parenthesis
        const disallowedValue = parts[1];

        if (ruleType.startsWith('custom')) {
          const disallowedChar = ruleType.split('-')[1]; // Extract the character after the '-'
          if (inputField.value.includes(disallowedChar)) {
            errorMessage += disallowedValue + '<br>'; // Add line break
          }
        } else if (ruleType === 'space' && inputField.value.includes(' ')) {
          errorMessage += disallowedValue + '<br>'; // Add line break
        } else if (ruleType === 'number' && /\d/.test(inputField.value)) {
          errorMessage += disallowedValue + '<br>'; // Add line break
        } else if (ruleType === 'special' && /[^a-zA-Z0-9\s]/.test(inputField.value)) { // Notice the \s here
          errorMessage += disallowedValue + '<br>'; // Add line break
        }
      });

      errorBlock.innerHTML = errorMessage || ''; // Use innerHTML to interpret <br> tags
    });
  });
});
</script>
View Memberscript
UX
Conditional Visibility

#74 - Styling with Link Parameters

Update page styling based on a link parameter. Ex. ?ms-code-target=CLASSNAME&ms-code-style=display:block


<!-- 💙 MEMBERSCRIPT #74 v0.1 💙 UPDATE STYLING WITH LINK PARAMS -->
<script>
    // Function to parse URL parameters
    function getURLParameter(name) {
        return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [null, ''])[1].replace(/\+/g, '%20')) || null;
    }

    // Function to apply styles
    function applyStylesFromURL() {
        const targetClass = getURLParameter('ms-code-target');
        const rawStyles = getURLParameter('ms-code-style');

        if (targetClass && rawStyles) {
            const elements = document.querySelectorAll(`.${targetClass}`);

            const styles = rawStyles.split(';').filter(style => style.trim() !== ''); // filter out any empty strings
            styles.forEach(style => {
                const [property, value] = style.split(':');
                elements.forEach(element => {
                    element.style[property] = value;
                });
            });
        }
    }

    // Call the function once the DOM is loaded
    window.addEventListener('DOMContentLoaded', (event) => {
        applyStylesFromURL();
    });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #74 v0.1 💙 UPDATE STYLING WITH LINK PARAMS -->
<script>
    // Function to parse URL parameters
    function getURLParameter(name) {
        return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [null, ''])[1].replace(/\+/g, '%20')) || null;
    }

    // Function to apply styles
    function applyStylesFromURL() {
        const targetClass = getURLParameter('ms-code-target');
        const rawStyles = getURLParameter('ms-code-style');

        if (targetClass && rawStyles) {
            const elements = document.querySelectorAll(`.${targetClass}`);

            const styles = rawStyles.split(';').filter(style => style.trim() !== ''); // filter out any empty strings
            styles.forEach(style => {
                const [property, value] = style.split(':');
                elements.forEach(element => {
                    element.style[property] = value;
                });
            });
        }
    }

    // Call the function once the DOM is loaded
    window.addEventListener('DOMContentLoaded', (event) => {
        applyStylesFromURL();
    });
</script>
View Memberscript
Marketing
UX

#73 - Display Date & Time

Display the current time, time-of-day, day, month, or year to a user. Works if logged in or logged out.


<!-- 💙 MEMBERSCRIPT #73 v0.1 💙 DATES AND TIMES -->

function getCurrentDateInfo(attribute) {
    const now = new Date();
    const options = { hour12: true };

    switch(attribute) {
        case "day":
            return now.toLocaleDateString('en-US', { weekday: 'long' });
        case "time":
            return now.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }).toLowerCase();
        case "month":
            return now.toLocaleDateString('en-US', { month: 'long' });
        case "year":
            return now.getFullYear().toString();
        case "time-of-day":
            const hour = now.getHours();
            if (5 <= hour && hour < 12) return "morning";
            if (12 <= hour && hour < 17) return "afternoon";
            if (17 <= hour && hour < 21) return "evening";
            return "night";
        default:
            return "Invalid attribute";
    }
}

function updateDateInfoOnPage() {
    const spanTags = document.querySelectorAll('span[ms-code-date]');

    spanTags.forEach(tag => {
        const attributeValue = tag.getAttribute('ms-code-date');
        const dateInfo = getCurrentDateInfo(attributeValue);
        tag.textContent = dateInfo;
    });
}

// Call the function to update the content on the page
updateDateInfoOnPage();
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #73 v0.1 💙 DATES AND TIMES -->

function getCurrentDateInfo(attribute) {
    const now = new Date();
    const options = { hour12: true };

    switch(attribute) {
        case "day":
            return now.toLocaleDateString('en-US', { weekday: 'long' });
        case "time":
            return now.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }).toLowerCase();
        case "month":
            return now.toLocaleDateString('en-US', { month: 'long' });
        case "year":
            return now.getFullYear().toString();
        case "time-of-day":
            const hour = now.getHours();
            if (5 <= hour && hour < 12) return "morning";
            if (12 <= hour && hour < 17) return "afternoon";
            if (17 <= hour && hour < 21) return "evening";
            return "night";
        default:
            return "Invalid attribute";
    }
}

function updateDateInfoOnPage() {
    const spanTags = document.querySelectorAll('span[ms-code-date]');

    spanTags.forEach(tag => {
        const attributeValue = tag.getAttribute('ms-code-date');
        const dateInfo = getCurrentDateInfo(attributeValue);
        tag.textContent = dateInfo;
    });
}

// Call the function to update the content on the page
updateDateInfoOnPage();
</script>
View Memberscript
Custom Fields
Conditional Visibility

#72 - Validate Original Values

Only allow a form to be submitted if the input value is original (ie. Usernames)


<!-- 💙 MEMBERSCRIPT #72 v0.1 💙 VALIDATE ORIGINAL VALUES -->

<style>
[ms-code-available="true"],
[ms-code-available="false"],
[ms-code-available="invalid"]{
    display: none;
}

.disabled {
    opacity: 0.5;
    pointer-events: none;
}
</style>

<script>
document.addEventListener('DOMContentLoaded', function() {
    let input = document.querySelector('[ms-code-available="input"]');
    let trueElement = document.querySelector('[ms-code-available="true"]');
    let falseElement = document.querySelector('[ms-code-available="false"]');
    let invalidElement = document.querySelector('[ms-code-available="invalid"]');
    let listElements = Array.from(document.querySelectorAll('[ms-code-available="list"]'));
    let submitButton = document.querySelector('[ms-code-available="submit"]');

    function checkUsername() {
        // Check if the input matches any of the list items
        let isTaken = listElements.some(elem => elem.textContent.trim() === input.value.trim());

        if (isTaken) {
            trueElement.style.display = 'none';
            falseElement.style.display = 'flex';
            submitButton.classList.add('disabled'); // disable the button if username is taken
        } else {
            trueElement.style.display = 'flex';
            falseElement.style.display = 'none';
            submitButton.classList.remove('disabled');
        }
    }

    input.addEventListener('input', function() {
        // Display the invalid element if input length is between 1 and 3
        if (input.value.length >= 1 && input.value.length <= 3) {
            invalidElement.style.display = 'flex';
        } else {
            invalidElement.style.display = 'none';
        }

        // Add the .disabled class to the submit button if input is empty or less than 3 characters
        if (input.value.length <= 3) {
            submitButton.classList.add('disabled');
            trueElement.style.display = 'none';
            falseElement.style.display = 'none';
        } else {
            checkUsername();
        }
    });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #72 v0.1 💙 VALIDATE ORIGINAL VALUES -->

<style>
[ms-code-available="true"],
[ms-code-available="false"],
[ms-code-available="invalid"]{
    display: none;
}

.disabled {
    opacity: 0.5;
    pointer-events: none;
}
</style>

<script>
document.addEventListener('DOMContentLoaded', function() {
    let input = document.querySelector('[ms-code-available="input"]');
    let trueElement = document.querySelector('[ms-code-available="true"]');
    let falseElement = document.querySelector('[ms-code-available="false"]');
    let invalidElement = document.querySelector('[ms-code-available="invalid"]');
    let listElements = Array.from(document.querySelectorAll('[ms-code-available="list"]'));
    let submitButton = document.querySelector('[ms-code-available="submit"]');

    function checkUsername() {
        // Check if the input matches any of the list items
        let isTaken = listElements.some(elem => elem.textContent.trim() === input.value.trim());

        if (isTaken) {
            trueElement.style.display = 'none';
            falseElement.style.display = 'flex';
            submitButton.classList.add('disabled'); // disable the button if username is taken
        } else {
            trueElement.style.display = 'flex';
            falseElement.style.display = 'none';
            submitButton.classList.remove('disabled');
        }
    }

    input.addEventListener('input', function() {
        // Display the invalid element if input length is between 1 and 3
        if (input.value.length >= 1 && input.value.length <= 3) {
            invalidElement.style.display = 'flex';
        } else {
            invalidElement.style.display = 'none';
        }

        // Add the .disabled class to the submit button if input is empty or less than 3 characters
        if (input.value.length <= 3) {
            submitButton.classList.add('disabled');
            trueElement.style.display = 'none';
            falseElement.style.display = 'none';
        } else {
            checkUsername();
        }
    });
});
</script>
View Memberscript
UX
Custom Fields

#71 - Redirect if Certain Fields are Empty

Redirect a member to an onboarding page if certain custom fields are empty.


<!-- 💙 MEMBERSCRIPT #71 v0.1 💙 REDIRECT IF FIELDS ARE EMPTY -->
<script>
  document.addEventListener('DOMContentLoaded', async function() {
    const memberstack = window.$memberstackDom;

    const onboardingPageUrl = '/onboarding'; // replace
    const customFieldKeys = 'custom-field-1,custom-field-2'; // replace

    // No need to edit past this line
    const member = await memberstack.getCurrentMember();
    if (!member) {
      return;
    }

    // If current page slug matches the redirect slug, exit the script
    const currentPageSlug = window.location.pathname;
    if (currentPageSlug === onboardingPageUrl) {
      return;
    }

    async function checkOnboardingStatus() {
      try {
        const memberData = await memberstack.updateMember({});
        const customFields = customFieldKeys.split(',');

        for (let field of customFields) {
          if (!memberData.data.customFields[field.trim()]) {
            // Redirect to onboarding page if the custom field is empty
            window.location.href = onboardingPageUrl;
            return;
          }
        }
      } catch (error) {
        console.error(`Error in checkOnboardingStatus function: ${error}`);
      }
    }

    // Check onboarding status and potentially redirect
    checkOnboardingStatus().catch(error => {
      console.error(`Error in MemberScript #71 initial functions: ${error}`);
    });
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #71 v0.1 💙 REDIRECT IF FIELDS ARE EMPTY -->
<script>
  document.addEventListener('DOMContentLoaded', async function() {
    const memberstack = window.$memberstackDom;

    const onboardingPageUrl = '/onboarding'; // replace
    const customFieldKeys = 'custom-field-1,custom-field-2'; // replace

    // No need to edit past this line
    const member = await memberstack.getCurrentMember();
    if (!member) {
      return;
    }

    // If current page slug matches the redirect slug, exit the script
    const currentPageSlug = window.location.pathname;
    if (currentPageSlug === onboardingPageUrl) {
      return;
    }

    async function checkOnboardingStatus() {
      try {
        const memberData = await memberstack.updateMember({});
        const customFields = customFieldKeys.split(',');

        for (let field of customFields) {
          if (!memberData.data.customFields[field.trim()]) {
            // Redirect to onboarding page if the custom field is empty
            window.location.href = onboardingPageUrl;
            return;
          }
        }
      } catch (error) {
        console.error(`Error in checkOnboardingStatus function: ${error}`);
      }
    }

    // Check onboarding status and potentially redirect
    checkOnboardingStatus().catch(error => {
      console.error(`Error in MemberScript #71 initial functions: ${error}`);
    });
  });
</script>
View Memberscript
Marketing
JSON
Conditional Visibility
UX

#70 - Hide Old/Seen CMS Items

Only show CMS items which are new to a particular member. If they've seen it, hide it.


<!-- 💙 MEMBERSCRIPT #70 v0.1 💙 HIDE OLD CMS ITEMS -->
<script>
  document.addEventListener('DOMContentLoaded', async function() {
    const memberstack = window.$memberstackDom;

    // Only proceed if a member is found
    const member = await memberstack.getCurrentMember();
    if (!member) {
      console.log('No member found in MemberScript #70, exiting script');
      return;
    }

    async function getCmsItemsFromJson() {
      try {
        const memberData = await memberstack.getMemberJSON();
        return memberData?.data?.cmsItems || [];
      } catch (error) {
        console.error(`Error in getCmsItemsFromJson function: ${error}`);
      }
    }

    async function updateCmsItemsInJson(newCmsItems) {
      try {
        const memberData = await memberstack.getMemberJSON();
        memberData.data = memberData.data || {};
        memberData.data.cmsItems = newCmsItems;
        console.log(`CMS items in JSON after update: ${JSON.stringify(newCmsItems)}`);
        await memberstack.updateMemberJSON({ json: memberData.data });
      } catch (error) {
        console.error(`Error in updateCmsItemsInJson function: ${error}`);
      }
    }

    async function hideSeenCmsItems() {
      try {
        const cmsItemsElements = document.querySelectorAll('[ms-code-cms-item]');
        const cmsItemsFromJson = await getCmsItemsFromJson();

        cmsItemsElements.forEach(element => {
          const cmsValue = element.getAttribute('ms-code-cms-item');
          
          if (cmsItemsFromJson.includes(cmsValue)) {
            element.style.display = 'none';
          } else {
            cmsItemsFromJson.push(cmsValue);
          }
        });

        // Update the CMS items in JSON after the checks
        await updateCmsItemsInJson(cmsItemsFromJson);

      } catch (error) {
        console.error(`Error in hideSeenCmsItems function: ${error}`);
      }
    }

    // Hide seen CMS items when the page loads
    hideSeenCmsItems().catch(error => {
      console.error(`Error in MemberScript #70 initial functions: ${error}`);
    });
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #70 v0.1 💙 HIDE OLD CMS ITEMS -->
<script>
  document.addEventListener('DOMContentLoaded', async function() {
    const memberstack = window.$memberstackDom;

    // Only proceed if a member is found
    const member = await memberstack.getCurrentMember();
    if (!member) {
      console.log('No member found in MemberScript #70, exiting script');
      return;
    }

    async function getCmsItemsFromJson() {
      try {
        const memberData = await memberstack.getMemberJSON();
        return memberData?.data?.cmsItems || [];
      } catch (error) {
        console.error(`Error in getCmsItemsFromJson function: ${error}`);
      }
    }

    async function updateCmsItemsInJson(newCmsItems) {
      try {
        const memberData = await memberstack.getMemberJSON();
        memberData.data = memberData.data || {};
        memberData.data.cmsItems = newCmsItems;
        console.log(`CMS items in JSON after update: ${JSON.stringify(newCmsItems)}`);
        await memberstack.updateMemberJSON({ json: memberData.data });
      } catch (error) {
        console.error(`Error in updateCmsItemsInJson function: ${error}`);
      }
    }

    async function hideSeenCmsItems() {
      try {
        const cmsItemsElements = document.querySelectorAll('[ms-code-cms-item]');
        const cmsItemsFromJson = await getCmsItemsFromJson();

        cmsItemsElements.forEach(element => {
          const cmsValue = element.getAttribute('ms-code-cms-item');
          
          if (cmsItemsFromJson.includes(cmsValue)) {
            element.style.display = 'none';
          } else {
            cmsItemsFromJson.push(cmsValue);
          }
        });

        // Update the CMS items in JSON after the checks
        await updateCmsItemsInJson(cmsItemsFromJson);

      } catch (error) {
        console.error(`Error in hideSeenCmsItems function: ${error}`);
      }
    }

    // Hide seen CMS items when the page loads
    hideSeenCmsItems().catch(error => {
      console.error(`Error in MemberScript #70 initial functions: ${error}`);
    });
  });
</script>
View Memberscript
Marketing
Modals
JSON
Conditional Visibility
UX

#69 - Notify Members of New CMS Items

Display an element when there are new CMS items.


<!-- 💙 MEMBERSCRIPT #69 v0.1 💙 DISPLAY ELEMENT IF NEW CMS ITEMS -->
<script>
  document.addEventListener('DOMContentLoaded', async function() {
    const memberstack = window.$memberstackDom;

    // Set this variable to 'YES' or 'NO' depending on whether you want the UI to be displayed for new users
    const displayForNewUsers = 'YES';

    // Only proceed if a member is found
    const member = await memberstack.getCurrentMember();
    if (!member) {
      console.log('No member found, exiting script');
      return;
    }

    async function getUpdatesIDFromJson() {
      try {
        const memberData = await memberstack.getMemberJSON();
        console.log(`Member data: ${JSON.stringify(memberData)}`);
        return memberData?.data?.updatesID || '';
      } catch (error) {
        console.error(`Error in getUpdatesIDFromJson function: ${error}`);
      }
    }

    async function updateUpdatesIDInJson(newUpdatesID) {
      try {
        const memberData = await memberstack.getMemberJSON();
        memberData.data = memberData.data || {};
        memberData.data.updatesID = newUpdatesID;
        console.log(`Updates ID in JSON after update: ${newUpdatesID}`);
        await memberstack.updateMemberJSON({ json: memberData.data });
      } catch (error) {
        console.error(`Error in updateUpdatesIDInJson function: ${error}`);
      }
    }

    async function checkAndUpdateUI() {
      try {
        const element = document.querySelector('[ms-code-update-item]');
        const cmsItem = element.textContent;
        console.log(`CMS item: ${cmsItem}`);

        // Get the current updates ID from JSON
        const updatesIDFromJson = await getUpdatesIDFromJson();
        console.log(`Updates ID from JSON: ${updatesIDFromJson}`);

        // Check displayForNewUsers variable to decide behavior
        if (displayForNewUsers === 'NO' && !updatesIDFromJson) {
          console.log('Updates ID from JSON is undefined, null, or empty, not changing UI');
          return;
        }

        if (cmsItem !== updatesIDFromJson) {
          const uiElements = document.querySelectorAll('[ms-code-update-ui]');
          uiElements.forEach(uiElement => {
            uiElement.style.display = 'block';
            uiElement.style.opacity = '1';
          });
        }

        // Update the updates ID in JSON after the UI has been updated
        await updateUpdatesIDInJson(cmsItem);

      } catch (error) {
        console.error(`Error in checkAndUpdateUI function: ${error}`);
      }
    }

    // Check and update UI when the page loads
    checkAndUpdateUI().catch(error => {
      console.error(`Error in initial functions: ${error}`);
    });
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #69 v0.1 💙 DISPLAY ELEMENT IF NEW CMS ITEMS -->
<script>
  document.addEventListener('DOMContentLoaded', async function() {
    const memberstack = window.$memberstackDom;

    // Set this variable to 'YES' or 'NO' depending on whether you want the UI to be displayed for new users
    const displayForNewUsers = 'YES';

    // Only proceed if a member is found
    const member = await memberstack.getCurrentMember();
    if (!member) {
      console.log('No member found, exiting script');
      return;
    }

    async function getUpdatesIDFromJson() {
      try {
        const memberData = await memberstack.getMemberJSON();
        console.log(`Member data: ${JSON.stringify(memberData)}`);
        return memberData?.data?.updatesID || '';
      } catch (error) {
        console.error(`Error in getUpdatesIDFromJson function: ${error}`);
      }
    }

    async function updateUpdatesIDInJson(newUpdatesID) {
      try {
        const memberData = await memberstack.getMemberJSON();
        memberData.data = memberData.data || {};
        memberData.data.updatesID = newUpdatesID;
        console.log(`Updates ID in JSON after update: ${newUpdatesID}`);
        await memberstack.updateMemberJSON({ json: memberData.data });
      } catch (error) {
        console.error(`Error in updateUpdatesIDInJson function: ${error}`);
      }
    }

    async function checkAndUpdateUI() {
      try {
        const element = document.querySelector('[ms-code-update-item]');
        const cmsItem = element.textContent;
        console.log(`CMS item: ${cmsItem}`);

        // Get the current updates ID from JSON
        const updatesIDFromJson = await getUpdatesIDFromJson();
        console.log(`Updates ID from JSON: ${updatesIDFromJson}`);

        // Check displayForNewUsers variable to decide behavior
        if (displayForNewUsers === 'NO' && !updatesIDFromJson) {
          console.log('Updates ID from JSON is undefined, null, or empty, not changing UI');
          return;
        }

        if (cmsItem !== updatesIDFromJson) {
          const uiElements = document.querySelectorAll('[ms-code-update-ui]');
          uiElements.forEach(uiElement => {
            uiElement.style.display = 'block';
            uiElement.style.opacity = '1';
          });
        }

        // Update the updates ID in JSON after the UI has been updated
        await updateUpdatesIDInJson(cmsItem);

      } catch (error) {
        console.error(`Error in checkAndUpdateUI function: ${error}`);
      }
    }

    // Check and update UI when the page loads
    checkAndUpdateUI().catch(error => {
      console.error(`Error in initial functions: ${error}`);
    });
  });
</script>
View Memberscript
Marketing
Custom Fields

#68 - Gift a Membership

Enable members to purchase gifts for their friends and family.

v0.1
View Memberscript
We couldn't find any scripts for that search... please try again.
Slack

Need help with MemberScripts? Join our 5,500+ Member Slack community! 🙌

MemberScripts are a community resource by Memberstack - if you need any help making them work with your project, please join the Memberstack 2.0 Slack and ask for help!

Join our Slack
Showcase

Explore real businesses who've succeeded with Memberstack

Don't just take our word for it - check out businesses of all sizes who rely on Memberstack for their authentication and payments.

View all success stories
Even Webflow uses Memberstack!
Start building

Start building your dreams

Memberstack is 100% free until you're ready to launch - so, what are you waiting for? Create your first app and start building today.