MemberScripts
An attribute-based solution to add features to your Webflow site.
Simply copy some code, add some attributes, and you're done.
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.

#17 - Populate Link From Custom Field
Use custom fields to populate link targets with one attribute!
<!-- 💙 MEMBERSCRIPT #17 v0.3 💙 ADD CUSTOM FIELD AS A LINK -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const memberData = JSON.parse(localStorage.getItem('_ms-mem') || '{}');
if (!memberData?.id) return;
document.querySelectorAll('[ms-code-field-link]').forEach(element => {
const fieldKey = element.getAttribute('ms-code-field-link');
const fieldValue = memberData.customFields?.[fieldKey]?.trim();
if (!fieldValue) {
element.style.display = 'none';
return;
}
try {
// Add protocol if missing and validate URL
const url = !/^https?:\/\//i.test(fieldValue) ? 'https://' + fieldValue : fieldValue;
new URL(url); // Will throw if invalid URL
element.href = url;
element.rel = 'noopener noreferrer';
element.target = '_blank';
} catch {
element.style.display = 'none';
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #17 v0.3 💙 ADD CUSTOM FIELD AS A LINK -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const memberData = JSON.parse(localStorage.getItem('_ms-mem') || '{}');
if (!memberData?.id) return;
document.querySelectorAll('[ms-code-field-link]').forEach(element => {
const fieldKey = element.getAttribute('ms-code-field-link');
const fieldValue = memberData.customFields?.[fieldKey]?.trim();
if (!fieldValue) {
element.style.display = 'none';
return;
}
try {
// Add protocol if missing and validate URL
const url = !/^https?:\/\//i.test(fieldValue) ? 'https://' + fieldValue : fieldValue;
new URL(url); // Will throw if invalid URL
element.href = url;
element.rel = 'noopener noreferrer';
element.target = '_blank';
} catch {
element.style.display = 'none';
}
});
});
</script>

#16 - End Session After X Minutes of Inactivity
This script redirects inactive users to a "Session Expired" page after a certain period of inactivity. A countdown time is displayed on the page and resets with page activity.
Add this code before the closing </body> tag any page which needs a countdown timer.
<!-- 💙 MEMBERSCRIPT #16 v0.2 💙 LOGOUT AFTER X MINUTES OF INACTIVITY -->
<script>
let logoutTimer;
let countdownInterval;
let initialTime;
function startLogoutTimer() {
// Get the logout time from the HTML element
const timeElement = document.querySelector('[ms-code-logout-timer]');
const timeParts = timeElement.textContent.split(':');
const minutes = parseInt(timeParts[0], 10);
const seconds = parseInt(timeParts[1], 10);
const LOGOUT_TIME = (minutes * 60 + seconds) * 1000; // Convert to milliseconds
// Store the initial time value
if (!initialTime) {
initialTime = LOGOUT_TIME;
}
// Clear the previous timer, if any
clearTimeout(logoutTimer);
clearInterval(countdownInterval);
let startTime = Date.now();
// Start a new timer to redirect the user after the specified time
logoutTimer = setTimeout(() => {
window.location.href = "/expired";
startLogoutTimer(); // Start the logout timer again
}, LOGOUT_TIME);
// Start a countdown timer
countdownInterval = setInterval(() => {
let elapsed = Date.now() - startTime;
let remaining = LOGOUT_TIME - elapsed;
updateCountdownDisplay(remaining);
}, 1000); // update every second
}
function updateCountdownDisplay(remainingTimeInMs) {
// convert ms to MM:SS format
let minutes = Math.floor(remainingTimeInMs / 1000 / 60);
let seconds = Math.floor((remainingTimeInMs / 1000) % 60);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
const timeElement = document.querySelector('[ms-code-logout-timer]');
timeElement.textContent = `${minutes}:${seconds}`;
}
// Call this function whenever the user interacts with the page
function resetLogoutTimer() {
const timeElement = document.querySelector('[ms-code-logout-timer]');
timeElement.textContent = formatTime(initialTime); // Reset to the initial time
startLogoutTimer();
}
function formatTime(timeInMs) {
let minutes = Math.floor(timeInMs / 1000 / 60);
let seconds = Math.floor((timeInMs / 1000) % 60);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
return `${minutes}:${seconds}`;
}
// Call this function when the user logs in
function cancelLogoutTimer() {
clearTimeout(logoutTimer);
clearInterval(countdownInterval);
}
// Start the timer when the page loads
startLogoutTimer();
// Add event listeners to reset timer on any page activity
document.addEventListener('mousemove', resetLogoutTimer);
document.addEventListener('keypress', resetLogoutTimer);
document.addEventListener('touchstart', resetLogoutTimer);
</script>
Add this code to your /expired page before the closing </body> tag.
<!-- 💙 MEMBERSCRIPT #16 v0.2 💙 LOGOUT AFTER X MINUTES OF INACTIVITY -->
<script>
window.addEventListener('load', () => {
window.$memberstackDom.getCurrentMember().then(async ({ data: member }) => {
if (member) {
try {
await window.$memberstackDom.logout();
console.log('Logged out successfully');
setTimeout(() => {
location.reload();
}, 1000); // Refresh the page 1 second after logout
} catch (error) {
console.error(error);
}
}
});
});
</script>
Add this code before the closing </body> tag any page which needs a countdown timer.
<!-- 💙 MEMBERSCRIPT #16 v0.2 💙 LOGOUT AFTER X MINUTES OF INACTIVITY -->
<script>
let logoutTimer;
let countdownInterval;
let initialTime;
function startLogoutTimer() {
// Get the logout time from the HTML element
const timeElement = document.querySelector('[ms-code-logout-timer]');
const timeParts = timeElement.textContent.split(':');
const minutes = parseInt(timeParts[0], 10);
const seconds = parseInt(timeParts[1], 10);
const LOGOUT_TIME = (minutes * 60 + seconds) * 1000; // Convert to milliseconds
// Store the initial time value
if (!initialTime) {
initialTime = LOGOUT_TIME;
}
// Clear the previous timer, if any
clearTimeout(logoutTimer);
clearInterval(countdownInterval);
let startTime = Date.now();
// Start a new timer to redirect the user after the specified time
logoutTimer = setTimeout(() => {
window.location.href = "/expired";
startLogoutTimer(); // Start the logout timer again
}, LOGOUT_TIME);
// Start a countdown timer
countdownInterval = setInterval(() => {
let elapsed = Date.now() - startTime;
let remaining = LOGOUT_TIME - elapsed;
updateCountdownDisplay(remaining);
}, 1000); // update every second
}
function updateCountdownDisplay(remainingTimeInMs) {
// convert ms to MM:SS format
let minutes = Math.floor(remainingTimeInMs / 1000 / 60);
let seconds = Math.floor((remainingTimeInMs / 1000) % 60);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
const timeElement = document.querySelector('[ms-code-logout-timer]');
timeElement.textContent = `${minutes}:${seconds}`;
}
// Call this function whenever the user interacts with the page
function resetLogoutTimer() {
const timeElement = document.querySelector('[ms-code-logout-timer]');
timeElement.textContent = formatTime(initialTime); // Reset to the initial time
startLogoutTimer();
}
function formatTime(timeInMs) {
let minutes = Math.floor(timeInMs / 1000 / 60);
let seconds = Math.floor((timeInMs / 1000) % 60);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
return `${minutes}:${seconds}`;
}
// Call this function when the user logs in
function cancelLogoutTimer() {
clearTimeout(logoutTimer);
clearInterval(countdownInterval);
}
// Start the timer when the page loads
startLogoutTimer();
// Add event listeners to reset timer on any page activity
document.addEventListener('mousemove', resetLogoutTimer);
document.addEventListener('keypress', resetLogoutTimer);
document.addEventListener('touchstart', resetLogoutTimer);
</script>
Add this code to your /expired page before the closing </body> tag.
<!-- 💙 MEMBERSCRIPT #16 v0.2 💙 LOGOUT AFTER X MINUTES OF INACTIVITY -->
<script>
window.addEventListener('load', () => {
window.$memberstackDom.getCurrentMember().then(async ({ data: member }) => {
if (member) {
try {
await window.$memberstackDom.logout();
console.log('Logged out successfully');
setTimeout(() => {
location.reload();
}, 1000); // Refresh the page 1 second after logout
} catch (error) {
console.error(error);
}
}
});
});
</script>

#15 - Refresh Page After Set Duration On Click
Refresh the page after a set amount of time when an element is clicked.
<!-- 💙 MEMBERSCRIPT #15 v0.1 💙 TRIGGER REFRESH ON CLICK -->
<script>
document.addEventListener("DOMContentLoaded", function() {
document.addEventListener("click", function(event) {
const clickedElement = event.target.closest('[ms-code-refresh]');
if (clickedElement) {
const refreshDelay = parseInt(clickedElement.getAttribute('ms-code-refresh'));
if (!isNaN(refreshDelay)) {
setTimeout(function() {
location.reload();
}, refreshDelay);
}
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #15 v0.1 💙 TRIGGER REFRESH ON CLICK -->
<script>
document.addEventListener("DOMContentLoaded", function() {
document.addEventListener("click", function(event) {
const clickedElement = event.target.closest('[ms-code-refresh]');
if (clickedElement) {
const refreshDelay = parseInt(clickedElement.getAttribute('ms-code-refresh'));
if (!isNaN(refreshDelay)) {
setTimeout(function() {
location.reload();
}, refreshDelay);
}
}
});
});
</script>

#14 - Create Loading State On Click
Simulate a custom loading state when an element is clicked.
<!-- 💙 MEMBERSCRIPT #14 v0.1 💙 CREATE LOADING STATE ON CLICK -->
<script src="https://code.jquery.com/jquery-3.7.0.min.js" integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous" ></script>
<script>
$(document).ready(function() {
// Bind click event to elements with ms-code-loading-trigger attribute
$(document).on('click', '[ms-code-loading-trigger]', function() {
// Get the value of ms-code-loading-trigger attribute
var triggerValue = $(this).attr("ms-code-loading-trigger");
// Find the matching loading element
var loadingElement = $("[ms-code-loading-element='" + triggerValue + "']");
// Find the matching subject element
var subjectElement = $("[ms-code-loading-subject='" + triggerValue + "']");
// Show the loading element (with flex display), append it to the subject element
loadingElement.css('display', 'flex').appendTo(subjectElement);
// After 5 seconds (5000 milliseconds), hide the loading element
setTimeout(function() {
loadingElement.hide();
}, 5000);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #14 v0.1 💙 CREATE LOADING STATE ON CLICK -->
<script src="https://code.jquery.com/jquery-3.7.0.min.js" integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous" ></script>
<script>
$(document).ready(function() {
// Bind click event to elements with ms-code-loading-trigger attribute
$(document).on('click', '[ms-code-loading-trigger]', function() {
// Get the value of ms-code-loading-trigger attribute
var triggerValue = $(this).attr("ms-code-loading-trigger");
// Find the matching loading element
var loadingElement = $("[ms-code-loading-element='" + triggerValue + "']");
// Find the matching subject element
var subjectElement = $("[ms-code-loading-subject='" + triggerValue + "']");
// Show the loading element (with flex display), append it to the subject element
loadingElement.css('display', 'flex').appendTo(subjectElement);
// After 5 seconds (5000 milliseconds), hide the loading element
setTimeout(function() {
loadingElement.hide();
}, 5000);
});
});
</script>

#13 - Filter JSON Item Groups
Show filtered lists to your members based on a JSON property!
<!-- 💙 MEMBERSCRIPT #13 v0.2 💙 FILTER ITEM GROUPS FROM JSON BASED ON ATTRIBUTE VALUE -->
<script>
(function() {
const memberstack = window.$memberstackDom;
let member;
const fetchMemberJSON = async function() {
member = await memberstack.getMemberJSON();
return member;
}
const filterItems = async function(printList) {
const filterAttr = printList.getAttribute('ms-code-list-filter');
if (!filterAttr) return;
const filters = filterAttr.split(',').map(filter => filter.trim());
const jsonGroup = printList.getAttribute('ms-code-print-list');
const items = member.data && member.data[jsonGroup] ? Object.values(member.data[jsonGroup]) : [];
const itemContainer = document.createElement('div');
const placeholder = printList.querySelector(`[ms-code-print-item="${jsonGroup}"]`);
const itemTemplate = placeholder.outerHTML;
items.forEach(item => {
const newItem = document.createElement('div');
newItem.innerHTML = itemTemplate;
const itemElements = newItem.querySelectorAll('[ms-code-item-text]');
let skipItem = false;
filters.forEach(filter => {
const exclude = filter.startsWith('!');
const filterKey = exclude ? filter.substring(1) : filter;
if ((exclude && item.hasOwnProperty(filterKey)) || (!exclude && !item.hasOwnProperty(filterKey))) {
skipItem = true;
}
});
if (skipItem) return; // Skip this item
itemElements.forEach(itemElement => {
const jsonKey = itemElement.getAttribute('ms-code-item-text');
const value = item && item[jsonKey] ? item[jsonKey] : '';
itemElement.textContent = value;
});
const itemKey = Object.keys(member.data[jsonGroup]).find(k => member.data[jsonGroup][k] === item);
newItem.firstChild.setAttribute('ms-code-item-key', itemKey);
itemContainer.appendChild(newItem.firstChild);
});
printList.innerHTML = itemContainer.innerHTML;
};
// Fetch member JSON
let intervalId = setInterval(async () => {
member = await fetchMemberJSON();
if (member && member.data) {
clearInterval(intervalId);
const printLists = document.querySelectorAll('[ms-code-print-list]');
printLists.forEach(filterItems);
}
}, 500);
// Add click event listener to elements with ms-code-update="json"
const updateButtons = document.querySelectorAll('[ms-code-update="json"]');
updateButtons.forEach(button => {
button.addEventListener("click", async function() {
// Fetch member JSON on each click
let intervalIdClick = setInterval(async () => {
member = await fetchMemberJSON();
if (member && member.data) {
clearInterval(intervalIdClick);
const printLists = document.querySelectorAll('[ms-code-print-list]');
printLists.forEach(filterItems);
}
}, 500);
});
});
})();
</script>
<!-- 💙 MEMBERSCRIPT #13 v0.2 💙 FILTER ITEM GROUPS FROM JSON BASED ON ATTRIBUTE VALUE -->
<script>
(function() {
const memberstack = window.$memberstackDom;
let member;
const fetchMemberJSON = async function() {
member = await memberstack.getMemberJSON();
return member;
}
const filterItems = async function(printList) {
const filterAttr = printList.getAttribute('ms-code-list-filter');
if (!filterAttr) return;
const filters = filterAttr.split(',').map(filter => filter.trim());
const jsonGroup = printList.getAttribute('ms-code-print-list');
const items = member.data && member.data[jsonGroup] ? Object.values(member.data[jsonGroup]) : [];
const itemContainer = document.createElement('div');
const placeholder = printList.querySelector(`[ms-code-print-item="${jsonGroup}"]`);
const itemTemplate = placeholder.outerHTML;
items.forEach(item => {
const newItem = document.createElement('div');
newItem.innerHTML = itemTemplate;
const itemElements = newItem.querySelectorAll('[ms-code-item-text]');
let skipItem = false;
filters.forEach(filter => {
const exclude = filter.startsWith('!');
const filterKey = exclude ? filter.substring(1) : filter;
if ((exclude && item.hasOwnProperty(filterKey)) || (!exclude && !item.hasOwnProperty(filterKey))) {
skipItem = true;
}
});
if (skipItem) return; // Skip this item
itemElements.forEach(itemElement => {
const jsonKey = itemElement.getAttribute('ms-code-item-text');
const value = item && item[jsonKey] ? item[jsonKey] : '';
itemElement.textContent = value;
});
const itemKey = Object.keys(member.data[jsonGroup]).find(k => member.data[jsonGroup][k] === item);
newItem.firstChild.setAttribute('ms-code-item-key', itemKey);
itemContainer.appendChild(newItem.firstChild);
});
printList.innerHTML = itemContainer.innerHTML;
};
// Fetch member JSON
let intervalId = setInterval(async () => {
member = await fetchMemberJSON();
if (member && member.data) {
clearInterval(intervalId);
const printLists = document.querySelectorAll('[ms-code-print-list]');
printLists.forEach(filterItems);
}
}, 500);
// Add click event listener to elements with ms-code-update="json"
const updateButtons = document.querySelectorAll('[ms-code-update="json"]');
updateButtons.forEach(button => {
button.addEventListener("click", async function() {
// Fetch member JSON on each click
let intervalIdClick = setInterval(async () => {
member = await fetchMemberJSON();
if (member && member.data) {
clearInterval(intervalIdClick);
const printLists = document.querySelectorAll('[ms-code-print-list]');
printLists.forEach(filterItems);
}
}, 500);
});
});
})();
</script>

#12 - Add Items To JSON Groups On Click
Add an item/property to previously created JSON items with one click!
<!-- 💙 MEMBERSCRIPT #12 v0.1 💙 ADD ITEM TO JSON GROUP ON CLICK -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const memberstack = window.$memberstackDom;
// Add click event listener to the document
document.addEventListener("click", async function(event) {
const target = event.target;
// Check if the clicked element has ms-code-update-json-item attribute
const updateJsonItem = target.closest('[ms-code-update-json-item]');
if (updateJsonItem) {
const key = updateJsonItem.closest('[ms-code-item-key]').getAttribute('ms-code-item-key');
const jsonGroup = updateJsonItem.closest('[ms-code-print-list]').getAttribute('ms-code-print-list');
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
if (member.data && member.data[jsonGroup] && member.data[jsonGroup][key]) {
// Get the value to be added to the item in the member JSON
const updateValue = updateJsonItem.getAttribute('ms-code-update-json-item');
// Update the member JSON with the new value
member.data[jsonGroup][key][updateValue] = true;
// Update the member JSON using the Memberstack SDK
await memberstack.updateMemberJSON({
json: member.data
});
// Optional: Display a success message or perform any other desired action
console.log('Member JSON updated successfully');
} else {
console.error(`Could not find item with key: ${key}`);
}
}
// Check if the clicked element has ms-code-update attribute
const updateButton = target.closest('[ms-code-update="json"]');
if (updateButton) {
// Add a delay
await new Promise(resolve => setTimeout(resolve, 500));
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
// Save the member JSON as a local storage item
localStorage.setItem("memberJSON", JSON.stringify(member));
// Optional: Display a success message or perform any other desired action
console.log('Member JSON saved to local storage');
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #12 v0.1 💙 ADD ITEM TO JSON GROUP ON CLICK -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const memberstack = window.$memberstackDom;
// Add click event listener to the document
document.addEventListener("click", async function(event) {
const target = event.target;
// Check if the clicked element has ms-code-update-json-item attribute
const updateJsonItem = target.closest('[ms-code-update-json-item]');
if (updateJsonItem) {
const key = updateJsonItem.closest('[ms-code-item-key]').getAttribute('ms-code-item-key');
const jsonGroup = updateJsonItem.closest('[ms-code-print-list]').getAttribute('ms-code-print-list');
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
if (member.data && member.data[jsonGroup] && member.data[jsonGroup][key]) {
// Get the value to be added to the item in the member JSON
const updateValue = updateJsonItem.getAttribute('ms-code-update-json-item');
// Update the member JSON with the new value
member.data[jsonGroup][key][updateValue] = true;
// Update the member JSON using the Memberstack SDK
await memberstack.updateMemberJSON({
json: member.data
});
// Optional: Display a success message or perform any other desired action
console.log('Member JSON updated successfully');
} else {
console.error(`Could not find item with key: ${key}`);
}
}
// Check if the clicked element has ms-code-update attribute
const updateButton = target.closest('[ms-code-update="json"]');
if (updateButton) {
// Add a delay
await new Promise(resolve => setTimeout(resolve, 500));
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
// Save the member JSON as a local storage item
localStorage.setItem("memberJSON", JSON.stringify(member));
// Optional: Display a success message or perform any other desired action
console.log('Member JSON saved to local storage');
}
});
});
</script>

#11 - Automatically Open Login Modal
Show all logged-out visitors a login modal immediately on page visit. Both custom and default Memberstack work.
<!-- 💙 MEMBERSCRIPT #11 v0.1 💙 SHOW LOGIN MODAL IF MEMBER IS NOT LOGGED IN -->
<!-- KEEP THIS FOR DEFAULT MEMBERSTACK MODAL -->
<script>
function handleRedirect(redirect) {
if (redirect && (window.location.pathname !== redirect)) return window.location.href = redirect;
window.location.reload()
}
$memberstackDom.getCurrentMember().then(async ({
data
}) => {
if (!data) {
const {
data
} = await $memberstackDom.openModal("login");
handleRedirect(data.redirect)
}
})
</script>
<!-- KEEP THIS FOR YOUR OWN CUSTOM MODAL -->
<script>
$memberstackDom.getCurrentMember().then(({ data }) => {
if (!data) {
const loginModal = document.querySelector('[ms-code-modal="login"]');
if (loginModal) {
loginModal.style.display = "flex";
}
}
});
</script>
<!-- 💙 MEMBERSCRIPT #11 v0.1 💙 SHOW LOGIN MODAL IF MEMBER IS NOT LOGGED IN -->
<!-- KEEP THIS FOR DEFAULT MEMBERSTACK MODAL -->
<script>
function handleRedirect(redirect) {
if (redirect && (window.location.pathname !== redirect)) return window.location.href = redirect;
window.location.reload()
}
$memberstackDom.getCurrentMember().then(async ({
data
}) => {
if (!data) {
const {
data
} = await $memberstackDom.openModal("login");
handleRedirect(data.redirect)
}
})
</script>
<!-- KEEP THIS FOR YOUR OWN CUSTOM MODAL -->
<script>
$memberstackDom.getCurrentMember().then(({ data }) => {
if (!data) {
const loginModal = document.querySelector('[ms-code-modal="login"]');
if (loginModal) {
loginModal.style.display = "flex";
}
}
});
</script>

#10 - Display/Remove Element based on Custom Field
Check if a member has filled out a custom field. If yes, set the target element to display.
<!-- 💙 MEMBERSCRIPT #10 v0.2 💙 HIDE ELEMENTS IF CUSTOM FIELD IS BLANK -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Get the `_ms-mem` object from the local storage
const msMem = JSON.parse(localStorage.getItem('_ms-mem'));
// Get all the elements that have the `ms-code-customfield` attribute
const elements = document.querySelectorAll('[ms-code-customfield]');
// Iterate over each element
elements.forEach(element => {
// Get the value of the `ms-code-customfield` attribute
const customField = element.getAttribute('ms-code-customfield');
// If customField starts with '!', we invert the logic
if (customField.startsWith('!')) {
const actualCustomField = customField.slice(1); // remove the '!' from the start
// If the custom field is empty, remove the element from the DOM
if (msMem.customFields && msMem.customFields[actualCustomField]) {
element.parentNode.removeChild(element);
}
} else {
// Check if the user has the corresponding custom field in Memberstack
if (!msMem.customFields || !msMem.customFields[customField]) {
// If the custom field is empty, remove the element from the DOM
element.parentNode.removeChild(element);
}
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #10 v0.2 💙 HIDE ELEMENTS IF CUSTOM FIELD IS BLANK -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Get the `_ms-mem` object from the local storage
const msMem = JSON.parse(localStorage.getItem('_ms-mem'));
// Get all the elements that have the `ms-code-customfield` attribute
const elements = document.querySelectorAll('[ms-code-customfield]');
// Iterate over each element
elements.forEach(element => {
// Get the value of the `ms-code-customfield` attribute
const customField = element.getAttribute('ms-code-customfield');
// If customField starts with '!', we invert the logic
if (customField.startsWith('!')) {
const actualCustomField = customField.slice(1); // remove the '!' from the start
// If the custom field is empty, remove the element from the DOM
if (msMem.customFields && msMem.customFields[actualCustomField]) {
element.parentNode.removeChild(element);
}
} else {
// Check if the user has the corresponding custom field in Memberstack
if (!msMem.customFields || !msMem.customFields[customField]) {
// If the custom field is empty, remove the element from the DOM
element.parentNode.removeChild(element);
}
}
});
});
</script>

#9 - Real, User Based Countdown
Dynamically set a per-user countdown time and then hide elements when time is up.
<!-- 💙 MEMBERSCRIPT #9 v0.1 💙 COUNTDOWN BY USER -->
<script>
// Step 1: Get the current date and time
const currentDate = new Date();
// Step 2: Calculate the new date and time based on the attribute values
const addTime = (date, unit, value) => {
const newDate = new Date(date);
switch (unit) {
case 'hour':
newDate.setHours(newDate.getHours() + value);
break;
case 'minute':
newDate.setMinutes(newDate.getMinutes() + value);
break;
case 'second':
newDate.setSeconds(newDate.getSeconds() + value);
break;
case 'millisecond':
newDate.setMilliseconds(newDate.getMilliseconds() + value);
break;
}
return newDate;
};
// Retrieve attribute values and calculate the new date and time
const hourAttr = document.querySelector('[ms-code-time-hour]');
const minuteAttr = document.querySelector('[ms-code-time-minute]');
const secondAttr = document.querySelector('[ms-code-time-second]');
const millisecondAttr = document.querySelector('[ms-code-time-millisecond]');
const countdownDateTime = localStorage.getItem('countdownDateTime');
let newDate;
if (countdownDateTime) {
newDate = new Date(countdownDateTime);
} else {
newDate = currentDate;
if (hourAttr.hasAttribute('ms-code-time-hour')) {
const hours = parseInt(hourAttr.getAttribute('ms-code-time-hour'));
newDate = addTime(newDate, 'hour', isNaN(hours) ? 0 : hours);
}
if (minuteAttr.hasAttribute('ms-code-time-minute')) {
const minutes = parseInt(minuteAttr.getAttribute('ms-code-time-minute'));
newDate = addTime(newDate, 'minute', isNaN(minutes) ? 0 : minutes);
}
if (secondAttr.hasAttribute('ms-code-time-second')) {
const seconds = parseInt(secondAttr.getAttribute('ms-code-time-second'));
newDate = addTime(newDate, 'second', isNaN(seconds) ? 0 : seconds);
}
if (millisecondAttr.hasAttribute('ms-code-time-millisecond')) {
const milliseconds = parseInt(millisecondAttr.getAttribute('ms-code-time-millisecond'));
newDate = addTime(newDate, 'millisecond', isNaN(milliseconds) ? 0 : milliseconds);
}
localStorage.setItem('countdownDateTime', newDate);
}
// Step 4: Update the text of the elements to show the continuous countdown
const countdownElements = [hourAttr, minuteAttr, secondAttr, millisecondAttr];
const updateCountdown = () => {
const currentTime = new Date();
const timeDifference = newDate - currentTime;
if (timeDifference > 0) {
const timeParts = [
Math.floor(timeDifference / (1000 * 60 * 60)) % 24, // Hours
Math.floor(timeDifference / (1000 * 60)) % 60, // Minutes
Math.floor(timeDifference / 1000) % 60, // Seconds
Math.floor(timeDifference) % 1000, // Milliseconds
];
// Update the text of the elements with the countdown values
countdownElements.forEach((element, index) => {
const timeValue = timeParts[index];
if (index === 3) {
element.innerText = timeValue.toString().padStart(2, '0').slice(0, 2); // Display only two digits for milliseconds
} else {
element.innerText = timeValue < 10 ? `0${timeValue}` : timeValue;
}
});
// Update the countdown every 10 milliseconds
setTimeout(updateCountdown, 10);
} else {
// Countdown has reached zero or has passed
countdownElements.forEach((element) => {
element.innerText = '00';
});
// Remove elements with ms-code-countdown="hide-on-end" attribute
const hideOnEndElements = document.querySelectorAll('[ms-code-countdown="hide-on-end"]');
hideOnEndElements.forEach((element) => {
element.remove();
});
// Optionally, you can perform additional actions or display a message when the countdown ends
}
};
// Start the countdown
updateCountdown();
</script>
<!-- 💙 MEMBERSCRIPT #9 v0.1 💙 COUNTDOWN BY USER -->
<script>
// Step 1: Get the current date and time
const currentDate = new Date();
// Step 2: Calculate the new date and time based on the attribute values
const addTime = (date, unit, value) => {
const newDate = new Date(date);
switch (unit) {
case 'hour':
newDate.setHours(newDate.getHours() + value);
break;
case 'minute':
newDate.setMinutes(newDate.getMinutes() + value);
break;
case 'second':
newDate.setSeconds(newDate.getSeconds() + value);
break;
case 'millisecond':
newDate.setMilliseconds(newDate.getMilliseconds() + value);
break;
}
return newDate;
};
// Retrieve attribute values and calculate the new date and time
const hourAttr = document.querySelector('[ms-code-time-hour]');
const minuteAttr = document.querySelector('[ms-code-time-minute]');
const secondAttr = document.querySelector('[ms-code-time-second]');
const millisecondAttr = document.querySelector('[ms-code-time-millisecond]');
const countdownDateTime = localStorage.getItem('countdownDateTime');
let newDate;
if (countdownDateTime) {
newDate = new Date(countdownDateTime);
} else {
newDate = currentDate;
if (hourAttr.hasAttribute('ms-code-time-hour')) {
const hours = parseInt(hourAttr.getAttribute('ms-code-time-hour'));
newDate = addTime(newDate, 'hour', isNaN(hours) ? 0 : hours);
}
if (minuteAttr.hasAttribute('ms-code-time-minute')) {
const minutes = parseInt(minuteAttr.getAttribute('ms-code-time-minute'));
newDate = addTime(newDate, 'minute', isNaN(minutes) ? 0 : minutes);
}
if (secondAttr.hasAttribute('ms-code-time-second')) {
const seconds = parseInt(secondAttr.getAttribute('ms-code-time-second'));
newDate = addTime(newDate, 'second', isNaN(seconds) ? 0 : seconds);
}
if (millisecondAttr.hasAttribute('ms-code-time-millisecond')) {
const milliseconds = parseInt(millisecondAttr.getAttribute('ms-code-time-millisecond'));
newDate = addTime(newDate, 'millisecond', isNaN(milliseconds) ? 0 : milliseconds);
}
localStorage.setItem('countdownDateTime', newDate);
}
// Step 4: Update the text of the elements to show the continuous countdown
const countdownElements = [hourAttr, minuteAttr, secondAttr, millisecondAttr];
const updateCountdown = () => {
const currentTime = new Date();
const timeDifference = newDate - currentTime;
if (timeDifference > 0) {
const timeParts = [
Math.floor(timeDifference / (1000 * 60 * 60)) % 24, // Hours
Math.floor(timeDifference / (1000 * 60)) % 60, // Minutes
Math.floor(timeDifference / 1000) % 60, // Seconds
Math.floor(timeDifference) % 1000, // Milliseconds
];
// Update the text of the elements with the countdown values
countdownElements.forEach((element, index) => {
const timeValue = timeParts[index];
if (index === 3) {
element.innerText = timeValue.toString().padStart(2, '0').slice(0, 2); // Display only two digits for milliseconds
} else {
element.innerText = timeValue < 10 ? `0${timeValue}` : timeValue;
}
});
// Update the countdown every 10 milliseconds
setTimeout(updateCountdown, 10);
} else {
// Countdown has reached zero or has passed
countdownElements.forEach((element) => {
element.innerText = '00';
});
// Remove elements with ms-code-countdown="hide-on-end" attribute
const hideOnEndElements = document.querySelectorAll('[ms-code-countdown="hide-on-end"]');
hideOnEndElements.forEach((element) => {
element.remove();
});
// Optionally, you can perform additional actions or display a message when the countdown ends
}
};
// Start the countdown
updateCountdown();
</script>

#8 - Copy To Clipboard
Allow visitors to copy things such as coupon codes to their clipboard with just one click.
<!-- 💙 MEMBERSCRIPT #8 v0.1 💙 SIMPLE COPY ELEMENT TO CLIPBOARD -->
<script>
// Step 1: Click the element with the attribute ms-code-copy="trigger"
const triggerElement = document.querySelector('[ms-code-copy="trigger"]');
triggerElement.addEventListener('click', () => {
// Step 2: Copy text from the element with the attribute ms-code-copy="subject" to the clipboard
const subjectElement = document.querySelector('[ms-code-copy="subject"]');
const subjectText = subjectElement.innerText;
// Create a temporary textarea element to facilitate the copying process
const tempTextArea = document.createElement('textarea');
tempTextArea.value = subjectText;
document.body.appendChild(tempTextArea);
// Select the text within the textarea and copy it to the clipboard
tempTextArea.select();
document.execCommand('copy');
// Remove the temporary textarea
document.body.removeChild(tempTextArea);
});
</script>
<!-- 💙 MEMBERSCRIPT #8 v0.1 💙 SIMPLE COPY ELEMENT TO CLIPBOARD -->
<script>
// Step 1: Click the element with the attribute ms-code-copy="trigger"
const triggerElement = document.querySelector('[ms-code-copy="trigger"]');
triggerElement.addEventListener('click', () => {
// Step 2: Copy text from the element with the attribute ms-code-copy="subject" to the clipboard
const subjectElement = document.querySelector('[ms-code-copy="subject"]');
const subjectText = subjectElement.innerText;
// Create a temporary textarea element to facilitate the copying process
const tempTextArea = document.createElement('textarea');
tempTextArea.value = subjectText;
document.body.appendChild(tempTextArea);
// Select the text within the textarea and copy it to the clipboard
tempTextArea.select();
document.execCommand('copy');
// Remove the temporary textarea
document.body.removeChild(tempTextArea);
});
</script>

#7 - Delay Loading Elements
Delay elements from appearing for a set duration to give the page time to update with correct member data.
<!-- 💙 MEMBERSCRIPT #7 v0.1 💙 DELAY LOADING ELEMENTS -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const elementsToDelay = document.querySelectorAll('[ms-code-delay]');
elementsToDelay.forEach(element => {
element.style.visibility = "hidden"; // Hide the element initially
});
setTimeout(function() {
elementsToDelay.forEach(element => {
element.style.visibility = "visible"; // Make the element visible after the delay
element.style.opacity = "0"; // Set the initial opacity to 0
element.style.animation = "fadeIn 0.5s"; // Apply the fadeIn animation
element.addEventListener("animationend", function() {
element.style.opacity = "1"; // Set opacity to 1 at the end of the animation
});
});
}, 1000);
});
</script>
<style>
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
</style>
<!-- 💙 MEMBERSCRIPT #7 v0.1 💙 DELAY LOADING ELEMENTS -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const elementsToDelay = document.querySelectorAll('[ms-code-delay]');
elementsToDelay.forEach(element => {
element.style.visibility = "hidden"; // Hide the element initially
});
setTimeout(function() {
elementsToDelay.forEach(element => {
element.style.visibility = "visible"; // Make the element visible after the delay
element.style.opacity = "0"; // Set the initial opacity to 0
element.style.animation = "fadeIn 0.5s"; // Apply the fadeIn animation
element.addEventListener("animationend", function() {
element.style.opacity = "1"; // Set opacity to 1 at the end of the animation
});
});
}, 1000);
});
</script>
<style>
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
</style>

#6 - Create Item Groups From Member JSON
Display JSON groups to logged in members using a placeholder element built in Webflow.
<!-- 💙 MEMBERSCRIPT #6 v0.1 💙 CREATE ITEM GROUPS FROM JSON -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
const memberstack = window.$memberstackDom;
// Function to display nested/sub items
const displayNestedItems = async function(printList) {
const jsonGroup = printList.getAttribute('ms-code-print-list');
const placeholder = printList.querySelector(`[ms-code-print-item="${jsonGroup}"]`);
if (!placeholder) return;
const itemTemplate = placeholder.outerHTML;
const itemContainer = document.createElement('div'); // Create a new container for the items
const member = await memberstack.getMemberJSON();
const items = member.data && member.data[jsonGroup] ? Object.values(member.data[jsonGroup]) : [];
if (items.length === 0) return; // If no items, exit the function
items.forEach(item => {
if (Object.keys(item).length === 0) return;
const newItem = document.createElement('div');
newItem.innerHTML = itemTemplate;
const itemElements = newItem.querySelectorAll('[ms-code-item-text]');
itemElements.forEach(itemElement => {
const jsonKey = itemElement.getAttribute('ms-code-item-text');
const value = item && item[jsonKey] ? item[jsonKey] : '';
itemElement.textContent = value;
});
// Add item key attribute
const itemKey = Object.keys(member.data[jsonGroup]).find(k => member.data[jsonGroup][k] === item);
newItem.firstChild.setAttribute('ms-code-item-key', itemKey);
itemContainer.appendChild(newItem.firstChild);
});
// Replace the existing list with the new items
printList.innerHTML = itemContainer.innerHTML;
};
// Call displayNestedItems function on initial page load for each instance
const printLists = document.querySelectorAll('[ms-code-print-list]');
printLists.forEach(printList => {
displayNestedItems(printList);
});
// Add click event listener to elements with ms-code-update="json"
const updateButtons = document.querySelectorAll('[ms-code-update="json"]');
updateButtons.forEach(button => {
button.addEventListener("click", async function() {
// Add a delay of 500ms
await new Promise(resolve => setTimeout(resolve, 500));
printLists.forEach(printList => {
displayNestedItems(printList);
});
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #6 v0.1 💙 CREATE ITEM GROUPS FROM JSON -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
const memberstack = window.$memberstackDom;
// Function to display nested/sub items
const displayNestedItems = async function(printList) {
const jsonGroup = printList.getAttribute('ms-code-print-list');
const placeholder = printList.querySelector(`[ms-code-print-item="${jsonGroup}"]`);
if (!placeholder) return;
const itemTemplate = placeholder.outerHTML;
const itemContainer = document.createElement('div'); // Create a new container for the items
const member = await memberstack.getMemberJSON();
const items = member.data && member.data[jsonGroup] ? Object.values(member.data[jsonGroup]) : [];
if (items.length === 0) return; // If no items, exit the function
items.forEach(item => {
if (Object.keys(item).length === 0) return;
const newItem = document.createElement('div');
newItem.innerHTML = itemTemplate;
const itemElements = newItem.querySelectorAll('[ms-code-item-text]');
itemElements.forEach(itemElement => {
const jsonKey = itemElement.getAttribute('ms-code-item-text');
const value = item && item[jsonKey] ? item[jsonKey] : '';
itemElement.textContent = value;
});
// Add item key attribute
const itemKey = Object.keys(member.data[jsonGroup]).find(k => member.data[jsonGroup][k] === item);
newItem.firstChild.setAttribute('ms-code-item-key', itemKey);
itemContainer.appendChild(newItem.firstChild);
});
// Replace the existing list with the new items
printList.innerHTML = itemContainer.innerHTML;
};
// Call displayNestedItems function on initial page load for each instance
const printLists = document.querySelectorAll('[ms-code-print-list]');
printLists.forEach(printList => {
displayNestedItems(printList);
});
// Add click event listener to elements with ms-code-update="json"
const updateButtons = document.querySelectorAll('[ms-code-update="json"]');
updateButtons.forEach(button => {
button.addEventListener("click", async function() {
// Add a delay of 500ms
await new Promise(resolve => setTimeout(resolve, 500));
printLists.forEach(printList => {
displayNestedItems(printList);
});
});
});
});
</script>

#5 - Fill Text Based On Simple JSON Item
Use Member JSON to update the text of any element on your page.
<!-- 💙 MEMBERSCRIPT #5 v0.1 💙 FILL TEXT BASED ON SIMPLE ITEM IN JSON -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const memberstack = window.$memberstackDom;
// Function to fill text elements with attribute ms-code-fill-text
const fillTextElements = async function() {
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
// Fill text elements with attribute ms-code-fill-text
const textElements = document.querySelectorAll('[ms-code-fill-text]');
textElements.forEach(element => {
const jsonKey = element.getAttribute('ms-code-fill-text');
const value = member.data && member.data[jsonKey] ? member.data[jsonKey] : '';
element.textContent = value;
});
};
// Function to handle script #4 event
const handleScript4Event = async function() {
// Add a delay of 500ms
await new Promise(resolve => setTimeout(resolve, 500));
await fillTextElements();
};
// Add click event listener to elements with ms-code-update="json"
const updateButtons = document.querySelectorAll('[ms-code-update="json"]');
updateButtons.forEach(button => {
button.addEventListener("click", handleScript4Event);
});
// Call fillTextElements function on initial page load
fillTextElements();
});
</script>
<!-- 💙 MEMBERSCRIPT #5 v0.1 💙 FILL TEXT BASED ON SIMPLE ITEM IN JSON -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const memberstack = window.$memberstackDom;
// Function to fill text elements with attribute ms-code-fill-text
const fillTextElements = async function() {
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
// Fill text elements with attribute ms-code-fill-text
const textElements = document.querySelectorAll('[ms-code-fill-text]');
textElements.forEach(element => {
const jsonKey = element.getAttribute('ms-code-fill-text');
const value = member.data && member.data[jsonKey] ? member.data[jsonKey] : '';
element.textContent = value;
});
};
// Function to handle script #4 event
const handleScript4Event = async function() {
// Add a delay of 500ms
await new Promise(resolve => setTimeout(resolve, 500));
await fillTextElements();
};
// Add click event listener to elements with ms-code-update="json"
const updateButtons = document.querySelectorAll('[ms-code-update="json"]');
updateButtons.forEach(button => {
button.addEventListener("click", handleScript4Event);
});
// Call fillTextElements function on initial page load
fillTextElements();
});
</script>

#4 - Update Member JSON In Local Storage On Click
Add this attribute to any button/element which you want to trigger a JSON update after a short delay.
<!-- 💙 MEMBERSCRIPT #4 v0.1 💙 UPDATE MEMBER JSON IN LOCAL STORAGE ON BUTTON CLICK -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const updateButtons = document.querySelectorAll('[ms-code-update="json"]');
updateButtons.forEach(button => {
button.addEventListener("click", async function() {
const memberstack = window.$memberstackDom;
// Add a delay
await new Promise(resolve => setTimeout(resolve, 500));
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
// Save the member JSON as a local storage item
localStorage.setItem("memberJSON", JSON.stringify(member));
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #4 v0.1 💙 UPDATE MEMBER JSON IN LOCAL STORAGE ON BUTTON CLICK -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const updateButtons = document.querySelectorAll('[ms-code-update="json"]');
updateButtons.forEach(button => {
button.addEventListener("click", async function() {
const memberstack = window.$memberstackDom;
// Add a delay
await new Promise(resolve => setTimeout(resolve, 500));
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
// Save the member JSON as a local storage item
localStorage.setItem("memberJSON", JSON.stringify(member));
});
});
});
</script>

#3 - Save Member JSON To Local Storage On Page Load
Create a localstorage object containing the logged in Member JSON on page load
<!-- 💙 MEMBERSCRIPT #3 v0.1 💙 SAVE JSON TO LOCALSTORAGE ON PAGE LOAD -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
const memberstack = window.$memberstackDom;
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
// Save the member JSON as a local storage item
localStorage.setItem("memberJSON", JSON.stringify(member));
});
</script>
<!-- 💙 MEMBERSCRIPT #3 v0.1 💙 SAVE JSON TO LOCALSTORAGE ON PAGE LOAD -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
const memberstack = window.$memberstackDom;
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
// Save the member JSON as a local storage item
localStorage.setItem("memberJSON", JSON.stringify(member));
});
</script>

#2 - Add Item Groups To Member JSON
Allow members to add nested items/groups to their Member JSON.
<!-- 💙 MEMBERSCRIPT #2 v0.1 💙 ADD ITEM GROUPS TO MEMBER JSON -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const msCodeForm2 = document.querySelector('[ms-code="form2"]');
const jsonList = msCodeForm2.getAttribute("ms-code-json-list");
msCodeForm2.addEventListener('submit', async function(event) {
event.preventDefault(); // Prevent the default form submission
// Retrieve the current member JSON data
const memberstack = window.$memberstackDom;
const member = await memberstack.getMemberJSON();
// Create a new member.data object if it doesn't already exist
if (!member.data) {
member.data = {};
}
// Check if the friends group already exists
if (!member.data[jsonList]) {
// Create a new friends group object
member.data[jsonList] = {};
}
// Retrieve the input values for creating the subgroup
const inputs = msCodeForm2.querySelectorAll('[ms-code-json-name]');
const subgroup = {};
inputs.forEach(input => {
const jsonName = input.getAttribute('ms-code-json-name');
const newItem = input.value;
subgroup[jsonName] = newItem;
});
// Generate a unique key for the new subgroup prefixed with the main group name
const timestamp = Date.now();
const subgroupKey = `${jsonList}-${timestamp}`;
// Create the new subgroup within the friends group
member.data[jsonList][subgroupKey] = subgroup;
// Update the member JSON with the new data
await memberstack.updateMemberJSON({
json: member.data
});
// Log a message to the console
console.log(`New subgroup with key ${subgroupKey} has been created within ${jsonList} group.`);
// Reset the input values
inputs.forEach(input => {
input.value = "";
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #2 v0.1 💙 ADD ITEM GROUPS TO MEMBER JSON -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const msCodeForm2 = document.querySelector('[ms-code="form2"]');
const jsonList = msCodeForm2.getAttribute("ms-code-json-list");
msCodeForm2.addEventListener('submit', async function(event) {
event.preventDefault(); // Prevent the default form submission
// Retrieve the current member JSON data
const memberstack = window.$memberstackDom;
const member = await memberstack.getMemberJSON();
// Create a new member.data object if it doesn't already exist
if (!member.data) {
member.data = {};
}
// Check if the friends group already exists
if (!member.data[jsonList]) {
// Create a new friends group object
member.data[jsonList] = {};
}
// Retrieve the input values for creating the subgroup
const inputs = msCodeForm2.querySelectorAll('[ms-code-json-name]');
const subgroup = {};
inputs.forEach(input => {
const jsonName = input.getAttribute('ms-code-json-name');
const newItem = input.value;
subgroup[jsonName] = newItem;
});
// Generate a unique key for the new subgroup prefixed with the main group name
const timestamp = Date.now();
const subgroupKey = `${jsonList}-${timestamp}`;
// Create the new subgroup within the friends group
member.data[jsonList][subgroupKey] = subgroup;
// Update the member JSON with the new data
await memberstack.updateMemberJSON({
json: member.data
});
// Log a message to the console
console.log(`New subgroup with key ${subgroupKey} has been created within ${jsonList} group.`);
// Reset the input values
inputs.forEach(input => {
input.value = "";
});
});
});
</script>

#1 - Add Items To Member JSON
Allow members to save simple items to their JSON without writing any code.
<!-- 💙 MEMBERSCRIPT #1 v0.1 💙 ADD INDIVIDUAL ITEMS TO MEMBER JSON -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const forms = document.querySelectorAll('[ms-code="form1"]');
forms.forEach(form => {
const jsonType = form.getAttribute("ms-code-json-type");
const jsonList = form.getAttribute("ms-code-json-list");
form.addEventListener('submit', async function(event) {
event.preventDefault(); // Prevent the default form submission
// Retrieve the current member JSON data
const memberstack = window.$memberstackDom;
const member = await memberstack.getMemberJSON();
// Create a new member.data object if it doesn't already exist
if (!member.data) {
member.data = {};
}
if (jsonType === "group") {
// Check if the group already exists
if (!member.data[jsonList]) {
// Create a new group object
member.data[jsonList] = {};
}
// Iterate over the inputs with ms-code-json-name attribute
const inputs = form.querySelectorAll('[ms-code-json-name]');
inputs.forEach(input => {
const jsonName = input.getAttribute('ms-code-json-name');
const newItem = input.value;
member.data[jsonList][jsonName] = newItem;
});
// Log a message to the console for group type
console.log(`Item(s) have been added to member JSON with group key ${jsonList}.`);
} else if (jsonType === "array") {
// Check if the array already exists
if (!member.data[jsonList]) {
// Create a new array
member.data[jsonList] = [];
}
// Retrieve the input values for the array type
const inputs = form.querySelectorAll('[ms-code-json-name]');
inputs.forEach(input => {
const jsonName = input.getAttribute('ms-code-json-name');
const newItem = input.value;
member.data[jsonList].push(newItem);
});
// Log a message to the console for array type
console.log(`Item(s) have been added to member JSON with array key ${jsonList}.`);
} else {
// Retrieve the input value and key for the basic item type
const input = form.querySelector('[ms-code-json-name]');
const jsonName = input.getAttribute('ms-code-json-name');
const newItem = input.value;
member.data[jsonName] = newItem;
// Log a message to the console for basic item type
console.log(`Item ${newItem} has been added to member JSON with key ${jsonName}.`);
}
// Update the member JSON with the new data
await memberstack.updateMemberJSON({
json: member.data
});
// Reset the input values
const inputs = form.querySelectorAll('[ms-code-json-name]');
inputs.forEach(input => {
input.value = "";
});
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #1 v0.1 💙 ADD INDIVIDUAL ITEMS TO MEMBER JSON -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const forms = document.querySelectorAll('[ms-code="form1"]');
forms.forEach(form => {
const jsonType = form.getAttribute("ms-code-json-type");
const jsonList = form.getAttribute("ms-code-json-list");
form.addEventListener('submit', async function(event) {
event.preventDefault(); // Prevent the default form submission
// Retrieve the current member JSON data
const memberstack = window.$memberstackDom;
const member = await memberstack.getMemberJSON();
// Create a new member.data object if it doesn't already exist
if (!member.data) {
member.data = {};
}
if (jsonType === "group") {
// Check if the group already exists
if (!member.data[jsonList]) {
// Create a new group object
member.data[jsonList] = {};
}
// Iterate over the inputs with ms-code-json-name attribute
const inputs = form.querySelectorAll('[ms-code-json-name]');
inputs.forEach(input => {
const jsonName = input.getAttribute('ms-code-json-name');
const newItem = input.value;
member.data[jsonList][jsonName] = newItem;
});
// Log a message to the console for group type
console.log(`Item(s) have been added to member JSON with group key ${jsonList}.`);
} else if (jsonType === "array") {
// Check if the array already exists
if (!member.data[jsonList]) {
// Create a new array
member.data[jsonList] = [];
}
// Retrieve the input values for the array type
const inputs = form.querySelectorAll('[ms-code-json-name]');
inputs.forEach(input => {
const jsonName = input.getAttribute('ms-code-json-name');
const newItem = input.value;
member.data[jsonList].push(newItem);
});
// Log a message to the console for array type
console.log(`Item(s) have been added to member JSON with array key ${jsonList}.`);
} else {
// Retrieve the input value and key for the basic item type
const input = form.querySelector('[ms-code-json-name]');
const jsonName = input.getAttribute('ms-code-json-name');
const newItem = input.value;
member.data[jsonName] = newItem;
// Log a message to the console for basic item type
console.log(`Item ${newItem} has been added to member JSON with key ${jsonName}.`);
}
// Update the member JSON with the new data
await memberstack.updateMemberJSON({
json: member.data
});
// Reset the input values
const inputs = form.querySelectorAll('[ms-code-json-name]');
inputs.forEach(input => {
input.value = "";
});
});
});
});
</script>
MemberScripts
Instantly add custom features to your Webflow site.
Just paste a script, set attributes, and go live.
Join the Memberstack 2.0 Slack for tips, answers, and community scripts. Please note that these are not official features and support cannot be guaranteed.

#58 - Price Range Slider Inputs
Create a price range input with individual inputs for min and max.
<!-- 💙 MEMBERSCRIPT #58 v0.1 💙 RANGE SLIDER INPUT -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.3.1/css/ion.rangeSlider.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.3.1/js/ion.rangeSlider.min.js"></script>
<style>
.irs {
font-family: inherit;
}
</style>
<script>
$(document).ready(function() {
var rangeSlider = $('[ms-code-input="range-slider"]');
var priceFromInput = $('[ms-code-input="price-from"]');
var priceToInput = $('[ms-code-input="price-to"]');
// Set the default range values
var defaultFrom = 20000;
var defaultTo = 50000;
rangeSlider.ionRangeSlider({
skin: "round", // You can also try "flat", "big", "modern", "sharp", or "square". Default styles can be overridden with CSS.
type: "double",
grid: true,
min: 0,
max: 100000,
from: defaultFrom,
to: defaultTo,
prefix: "$",
onStart: function(data) {
priceFromInput.val(data.from);
priceToInput.val(data.to);
},
onChange: function(data) {
priceFromInput.val(data.from);
priceToInput.val(data.to);
}
});
// Get the initial range values and update the inputs
var initialRange = rangeSlider.data("ionRangeSlider");
var initialData = initialRange.result;
priceFromInput.val(initialData.from);
priceToInput.val(initialData.to);
// Update the range slider and inputs when the inputs lose focus
priceFromInput.on("blur", function() {
var value = $(this).val();
var toValue = priceToInput.val();
// Perform validation
if (
value < initialRange.options.min ||
value > initialRange.options.max ||
value >= toValue ||
value > initialData.to // Check if fromValue is higher than the current toValue
) {
value = defaultFrom;
}
rangeSlider.data("ionRangeSlider").update({
from: value
});
priceFromInput.val(value);
});
priceToInput.on("blur", function() {
var value = $(this).val();
var fromValue = priceFromInput.val();
// Perform validation
if (
value < initialRange.options.min ||
value > initialRange.options.max ||
value <= fromValue ||
value < initialData.from // Check if toValue is lower than the current fromValue
) {
value = defaultTo;
}
rangeSlider.data("ionRangeSlider").update({
to: value
});
priceToInput.val(value);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #58 v0.1 💙 RANGE SLIDER INPUT -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.3.1/css/ion.rangeSlider.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.3.1/js/ion.rangeSlider.min.js"></script>
<style>
.irs {
font-family: inherit;
}
</style>
<script>
$(document).ready(function() {
var rangeSlider = $('[ms-code-input="range-slider"]');
var priceFromInput = $('[ms-code-input="price-from"]');
var priceToInput = $('[ms-code-input="price-to"]');
// Set the default range values
var defaultFrom = 20000;
var defaultTo = 50000;
rangeSlider.ionRangeSlider({
skin: "round", // You can also try "flat", "big", "modern", "sharp", or "square". Default styles can be overridden with CSS.
type: "double",
grid: true,
min: 0,
max: 100000,
from: defaultFrom,
to: defaultTo,
prefix: "$",
onStart: function(data) {
priceFromInput.val(data.from);
priceToInput.val(data.to);
},
onChange: function(data) {
priceFromInput.val(data.from);
priceToInput.val(data.to);
}
});
// Get the initial range values and update the inputs
var initialRange = rangeSlider.data("ionRangeSlider");
var initialData = initialRange.result;
priceFromInput.val(initialData.from);
priceToInput.val(initialData.to);
// Update the range slider and inputs when the inputs lose focus
priceFromInput.on("blur", function() {
var value = $(this).val();
var toValue = priceToInput.val();
// Perform validation
if (
value < initialRange.options.min ||
value > initialRange.options.max ||
value >= toValue ||
value > initialData.to // Check if fromValue is higher than the current toValue
) {
value = defaultFrom;
}
rangeSlider.data("ionRangeSlider").update({
from: value
});
priceFromInput.val(value);
});
priceToInput.on("blur", function() {
var value = $(this).val();
var fromValue = priceFromInput.val();
// Perform validation
if (
value < initialRange.options.min ||
value > initialRange.options.max ||
value <= fromValue ||
value < initialData.from // Check if toValue is lower than the current fromValue
) {
value = defaultTo;
}
rangeSlider.data("ionRangeSlider").update({
to: value
});
priceToInput.val(value);
});
});
</script>

#57 - Time Picker Input
Add a time picker to your form and prefill the time into a field.
<!-- 💙 MEMBERSCRIPT #57 v0.1 💙 TIME PICKER -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<link rel="stylesheet" href="https://uicdn.toast.com/tui.time-picker/latest/tui-time-picker.css">
<script src="https://uicdn.toast.com/tui.time-picker/latest/tui-time-picker.js"> </script>
<script>
$(document).ready(function() {
var tpSpinbox = new tui.TimePicker(document.querySelector('[ms-code-timepicker="box"]'), {
inputType: 'spinbox',
showMeridiem: true // If you don't use AM/PM remove this line
});
// Setup an event handler for when the time is selected
tpSpinbox.on('change', function() {
// Get the selected time
var hour = tpSpinbox.getHour();
var minute = tpSpinbox.getMinute();
var selectedTime = hour + ':' + (minute < 10 ? '0' : '') + minute;
// Update the value of the input element
document.querySelector('[ms-code-timepicker="input"]').value = selectedTime;
});
});
</script>
<!-- 💙 MEMBERSCRIPT #57 v0.1 💙 TIME PICKER -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<link rel="stylesheet" href="https://uicdn.toast.com/tui.time-picker/latest/tui-time-picker.css">
<script src="https://uicdn.toast.com/tui.time-picker/latest/tui-time-picker.js"> </script>
<script>
$(document).ready(function() {
var tpSpinbox = new tui.TimePicker(document.querySelector('[ms-code-timepicker="box"]'), {
inputType: 'spinbox',
showMeridiem: true // If you don't use AM/PM remove this line
});
// Setup an event handler for when the time is selected
tpSpinbox.on('change', function() {
// Get the selected time
var hour = tpSpinbox.getHour();
var minute = tpSpinbox.getMinute();
var selectedTime = hour + ':' + (minute < 10 ? '0' : '') + minute;
// Update the value of the input element
document.querySelector('[ms-code-timepicker="input"]').value = selectedTime;
});
});
</script>

#56 - Input Option Pairs
Combine the values of multiple inputs into one field.
<!-- 💙 MEMBERSCRIPT #56 v0.1 💙 INPUT OPTION PAIRS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<script>
$(document).ready(function() {
var groups = {};
// Get all inputs with the attribute ms-code-combine-inputs
var inputs = $('input[ms-code-combine-inputs], select[ms-code-combine-inputs]');
// For each input
inputs.each(function() {
// Split the attribute value at the dash
var parts = $(this).attr('ms-code-combine-inputs').split('-');
// If the group doesn't exist yet, create it
if (!groups[parts[0]]) {
groups[parts[0]] = {
targets: [],
values: [],
};
}
// If it's a target, add it to the targets
if (parts[1] == 'target') {
groups[parts[0]].targets.push($(this));
} else {
// It's an input, add it to the values and attach a listener
groups[parts[0]].values.push($(this));
$(this).on('input change', function() {
// On input or change, combine all values with a space in between
// and set the targets' value
var combinedValue = '';
$.each(groups[parts[0]].values, function(index, value) {
combinedValue += $(this).val();
if (index < groups[parts[0]].values.length - 1) {
combinedValue += ' '; // Add a space between values
}
});
$.each(groups[parts[0]].targets, function() {
$(this).val(combinedValue);
});
});
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #56 v0.1 💙 INPUT OPTION PAIRS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<script>
$(document).ready(function() {
var groups = {};
// Get all inputs with the attribute ms-code-combine-inputs
var inputs = $('input[ms-code-combine-inputs], select[ms-code-combine-inputs]');
// For each input
inputs.each(function() {
// Split the attribute value at the dash
var parts = $(this).attr('ms-code-combine-inputs').split('-');
// If the group doesn't exist yet, create it
if (!groups[parts[0]]) {
groups[parts[0]] = {
targets: [],
values: [],
};
}
// If it's a target, add it to the targets
if (parts[1] == 'target') {
groups[parts[0]].targets.push($(this));
} else {
// It's an input, add it to the values and attach a listener
groups[parts[0]].values.push($(this));
$(this).on('input change', function() {
// On input or change, combine all values with a space in between
// and set the targets' value
var combinedValue = '';
$.each(groups[parts[0]].values, function(index, value) {
combinedValue += $(this).val();
if (index < groups[parts[0]].values.length - 1) {
combinedValue += ' '; // Add a space between values
}
});
$.each(groups[parts[0]].targets, function() {
$(this).val(combinedValue);
});
});
}
});
});
</script>

#55 - Change Checkbox Parent Styles
Change the styles of another element when a checkbox is checked.
<!-- 💙 MEMBERSCRIPT #55 v0.1 💙 UPDATE CHECKBOX PARENT STYLES -->
<script>
// Wait for the DOM content to load
document.addEventListener('DOMContentLoaded', function() {
// Get all the checkbox elements
var checkboxes = document.querySelectorAll('[ms-code-checkbox="check"]');
// Iterate over each checkbox element
checkboxes.forEach(function(checkbox) {
// Get the boolean wrap element associated with the current checkbox
var booleanWrap = checkbox.closest('[ms-code-checkbox="wrap"]');
// Function to update the boolean wrap class based on checkbox state
function updateBooleanWrapClass() {
if (checkbox.checked) {
booleanWrap.classList.add('checked');
} else {
booleanWrap.classList.remove('checked');
}
}
// Check the initial value of the checkbox
updateBooleanWrapClass();
// Add an event listener to the checkbox to handle changes
checkbox.addEventListener('change', function() {
updateBooleanWrapClass();
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #55 v0.1 💙 UPDATE CHECKBOX PARENT STYLES -->
<script>
// Wait for the DOM content to load
document.addEventListener('DOMContentLoaded', function() {
// Get all the checkbox elements
var checkboxes = document.querySelectorAll('[ms-code-checkbox="check"]');
// Iterate over each checkbox element
checkboxes.forEach(function(checkbox) {
// Get the boolean wrap element associated with the current checkbox
var booleanWrap = checkbox.closest('[ms-code-checkbox="wrap"]');
// Function to update the boolean wrap class based on checkbox state
function updateBooleanWrapClass() {
if (checkbox.checked) {
booleanWrap.classList.add('checked');
} else {
booleanWrap.classList.remove('checked');
}
}
// Check the initial value of the checkbox
updateBooleanWrapClass();
// Add an event listener to the checkbox to handle changes
checkbox.addEventListener('change', function() {
updateBooleanWrapClass();
});
});
});
</script>

#54 - Checkbox Form Field Logic
Block other fields/items if a checkbox is not checked.
<!-- 💙 MEMBERSCRIPT #54 v0.1 💙 CHECKBOX FIELD FORM LOGIC -->
<style>
.disabled {
pointer-events: none;
opacity: 0.5;
}
</style>
<script>
// Wait for the DOM content to load
document.addEventListener('DOMContentLoaded', function() {
// Get all the trigger checkboxes
var triggerCheckboxes = document.querySelectorAll('[ms-code-field-logic-trigger]');
// Iterate over each trigger checkbox
triggerCheckboxes.forEach(function(checkbox) {
// Get the value of the trigger checkbox's attribute
var triggerValue = checkbox.getAttribute('ms-code-field-logic-trigger');
// Function to update the target elements' class based on checkbox state
function updateTargetElementsClass() {
// Find the associated target elements based on the attribute value
var targetElements = document.querySelectorAll('[ms-code-field-logic-target="' + triggerValue + '"]');
// Check the new value of the trigger checkbox
if (!checkbox.checked) {
// Add the "disabled" class to each target element
targetElements.forEach(function(targetElement) {
targetElement.classList.add('disabled');
});
} else {
// Remove the "disabled" class from each target element
targetElements.forEach(function(targetElement) {
targetElement.classList.remove('disabled');
});
}
}
// Check the initial value of the trigger checkbox
updateTargetElementsClass();
// Add an event listener to the trigger checkbox to handle changes
checkbox.addEventListener('change', function() {
updateTargetElementsClass();
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #54 v0.1 💙 CHECKBOX FIELD FORM LOGIC -->
<style>
.disabled {
pointer-events: none;
opacity: 0.5;
}
</style>
<script>
// Wait for the DOM content to load
document.addEventListener('DOMContentLoaded', function() {
// Get all the trigger checkboxes
var triggerCheckboxes = document.querySelectorAll('[ms-code-field-logic-trigger]');
// Iterate over each trigger checkbox
triggerCheckboxes.forEach(function(checkbox) {
// Get the value of the trigger checkbox's attribute
var triggerValue = checkbox.getAttribute('ms-code-field-logic-trigger');
// Function to update the target elements' class based on checkbox state
function updateTargetElementsClass() {
// Find the associated target elements based on the attribute value
var targetElements = document.querySelectorAll('[ms-code-field-logic-target="' + triggerValue + '"]');
// Check the new value of the trigger checkbox
if (!checkbox.checked) {
// Add the "disabled" class to each target element
targetElements.forEach(function(targetElement) {
targetElement.classList.add('disabled');
});
} else {
// Remove the "disabled" class from each target element
targetElements.forEach(function(targetElement) {
targetElement.classList.remove('disabled');
});
}
}
// Check the initial value of the trigger checkbox
updateTargetElementsClass();
// Add an event listener to the trigger checkbox to handle changes
checkbox.addEventListener('change', function() {
updateTargetElementsClass();
});
});
});
</script>

#53 - Update JSON Items With A Form
Allow your members to change details about their JSON items.
<!-- 💙 MEMBERSCRIPT #53 v0.1 💙 UPDATE JSON ITEMS WITH A FORM -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const memberstack = window.$memberstackDom;
// Add click event listener to the document
document.addEventListener("click", async function(event) {
const target = event.target;
// Check if the clicked element has ms-code-edit-item attribute
const editItem = target.closest('[ms-code-edit-item="prompt"]');
if (editItem) {
// Get the item key from the closest ancestor element with ms-code-item-key attribute
const key = editItem.closest('[ms-code-item-key]').getAttribute('ms-code-item-key');
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
// SET THE TARGET - EDIT ME
let targetObject = member.data.projects; // Update this line with the desired target location
if (member.data && targetObject && targetObject[key]) {
// Get the form element with the ms-code-edit-item="form" attribute
const form = document.querySelector('form[ms-code-edit-item="form"]');
if (form) {
// Loop through the form fields
for (const field of form.elements) {
const jsonName = field.getAttribute('ms-code-json-name');
if (jsonName && targetObject[key].hasOwnProperty(jsonName)) {
// Pre-fill the form field with the corresponding value from the JSON item
field.value = targetObject[key][jsonName];
}
}
// Get the modal element with the ms-code-edit-item="modal" attribute
const modal = document.querySelector('[ms-code-edit-item="modal"]');
if (modal) {
// Set the display property of the modal to flex
modal.style.display = 'flex';
}
// Add submit event listener to the form
form.addEventListener("submit", async function(event) {
event.preventDefault(); // Prevent the form from submitting normally
// Create an object to hold the updated values
const updatedValues = {};
// Loop through the form fields
for (const field of form.elements) {
const jsonName = field.getAttribute('ms-code-json-name');
if (jsonName) {
// Update the corresponding value in the updatedValues object
updatedValues[jsonName] = field.value;
}
}
// Update the target object with the new values
targetObject[key] = { ...targetObject[key], ...updatedValues };
// Update the member JSON using the Memberstack SDK
await memberstack.updateMemberJSON({
json: member.data
});
// Optional: Display a success message or perform any other desired action
console.log('Member JSON updated successfully');
});
} else {
console.error('Form element not found');
}
} else {
console.error(`Could not find item with key: ${key}`);
}
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #53 v0.1 💙 UPDATE JSON ITEMS WITH A FORM -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const memberstack = window.$memberstackDom;
// Add click event listener to the document
document.addEventListener("click", async function(event) {
const target = event.target;
// Check if the clicked element has ms-code-edit-item attribute
const editItem = target.closest('[ms-code-edit-item="prompt"]');
if (editItem) {
// Get the item key from the closest ancestor element with ms-code-item-key attribute
const key = editItem.closest('[ms-code-item-key]').getAttribute('ms-code-item-key');
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
// SET THE TARGET - EDIT ME
let targetObject = member.data.projects; // Update this line with the desired target location
if (member.data && targetObject && targetObject[key]) {
// Get the form element with the ms-code-edit-item="form" attribute
const form = document.querySelector('form[ms-code-edit-item="form"]');
if (form) {
// Loop through the form fields
for (const field of form.elements) {
const jsonName = field.getAttribute('ms-code-json-name');
if (jsonName && targetObject[key].hasOwnProperty(jsonName)) {
// Pre-fill the form field with the corresponding value from the JSON item
field.value = targetObject[key][jsonName];
}
}
// Get the modal element with the ms-code-edit-item="modal" attribute
const modal = document.querySelector('[ms-code-edit-item="modal"]');
if (modal) {
// Set the display property of the modal to flex
modal.style.display = 'flex';
}
// Add submit event listener to the form
form.addEventListener("submit", async function(event) {
event.preventDefault(); // Prevent the form from submitting normally
// Create an object to hold the updated values
const updatedValues = {};
// Loop through the form fields
for (const field of form.elements) {
const jsonName = field.getAttribute('ms-code-json-name');
if (jsonName) {
// Update the corresponding value in the updatedValues object
updatedValues[jsonName] = field.value;
}
}
// Update the target object with the new values
targetObject[key] = { ...targetObject[key], ...updatedValues };
// Update the member JSON using the Memberstack SDK
await memberstack.updateMemberJSON({
json: member.data
});
// Optional: Display a success message or perform any other desired action
console.log('Member JSON updated successfully');
});
} else {
console.error('Form element not found');
}
} else {
console.error(`Could not find item with key: ${key}`);
}
}
});
});
</script>

#52 - Delayed Page Redirect
Redirect members to a new page with an optional delay.
<!-- 💙 MEMBERSCRIPT #52 v0.1 💙 DELAYED REDIRECT TO NEW PAGE -->
<script>
const redirectToNewPage = function() {
setTimeout(function() {
window.location.href = "/your-page";
}, 1000); // Delay in milliseconds
};
redirectToNewPage();
</script>
<!-- 💙 MEMBERSCRIPT #52 v0.1 💙 DELAYED REDIRECT TO NEW PAGE -->
<script>
const redirectToNewPage = function() {
setTimeout(function() {
window.location.href = "/your-page";
}, 1000); // Delay in milliseconds
};
redirectToNewPage();
</script>

#51 - Display Member Metadata
Display member metadata dynamically on your website.
<!-- 💙 MEMBERSCRIPT #51 v0.2 💙 DISPLAY MEMBER METADATA -->
<script>
function replaceTextWithMetadata(metadata) {
var els = Array.from(document.querySelectorAll('[ms-code-member-meta]'));
els.forEach((el) => {
const key = el.getAttribute('ms-code-member-meta');
const value = metadata[key];
if (value !== undefined) {
el.innerHTML = value;
el.value = value;
el.src = value;
}
});
}
const memberstack = window.$memberstackDom;
memberstack.getCurrentMember()
.then(({ data: member }) => {
if (member && member.metaData) {
replaceTextWithMetadata(member.metaData);
}
})
.catch((error) => {
console.error('Error retrieving member data:', error);
});
</script>
<!-- 💙 MEMBERSCRIPT #51 v0.2 💙 DISPLAY MEMBER METADATA -->
<script>
function replaceTextWithMetadata(metadata) {
var els = Array.from(document.querySelectorAll('[ms-code-member-meta]'));
els.forEach((el) => {
const key = el.getAttribute('ms-code-member-meta');
const value = metadata[key];
if (value !== undefined) {
el.innerHTML = value;
el.value = value;
el.src = value;
}
});
}
const memberstack = window.$memberstackDom;
memberstack.getCurrentMember()
.then(({ data: member }) => {
if (member && member.metaData) {
replaceTextWithMetadata(member.metaData);
}
})
.catch((error) => {
console.error('Error retrieving member data:', error);
});
</script>

#50 - Cross-Device Dark Mode
Persistent dark mode option which stays working on your members' different devices.
Head Code
Put this in the <head> section of your site.
<!-- 💙 MEMBERSCRIPT #50 HEAD CODE v0.1 💙 CROSS-DEVICE DARK MODE -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const themePreference = localStorage.getItem('themePreference');
if (themePreference === 'dark') {
document.body.classList.add('dark');
}
});
</script>
Body Code
Put this in the </body> section of your site.
<!-- 💙 MEMBERSCRIPT #50 BODY CODE v0.1 💙 CROSS-DEVICE DARK MODE -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const darkModeToggle = document.querySelector('[ms-code-dark-mode="toggle"]');
const bodyElement = document.querySelector('.body');
// Function to check if theme preference is saved in member JSON
function checkThemePreference() {
const memberstack = window.$memberstackDom;
memberstack.getMemberJSON()
.then(function(memberData) {
const themePreference = memberData.data?.themePreference;
if (themePreference === 'dark') {
enableDarkMode();
} else {
disableDarkMode();
}
})
.catch(function(error) {
console.error('Error retrieving member data:', error);
});
}
// Function to enable dark mode
function enableDarkMode() {
darkModeToggle.classList.add('dark');
bodyElement.classList.add('dark');
updateThemePreference('dark');
}
// Function to disable dark mode
function disableDarkMode() {
darkModeToggle.classList.remove('dark');
bodyElement.classList.remove('dark');
updateThemePreference('light');
}
// Function to update theme preference in member JSON
function updateThemePreference(themePreference) {
const memberstack = window.$memberstackDom;
memberstack.getMemberJSON()
.then(function(memberData) {
memberData.data = memberData.data || {};
memberData.data.themePreference = themePreference;
memberstack.updateMemberJSON({ json: memberData.data })
.then(function() {
localStorage.setItem('themePreference', themePreference);
})
.catch(function(error) {
console.error('Error updating member data:', error);
});
})
.catch(function(error) {
console.error('Error retrieving member data:', error);
});
}
// Event listener for dark mode toggle
darkModeToggle.addEventListener('click', function() {
if (darkModeToggle.classList.contains('dark')) {
disableDarkMode();
} else {
enableDarkMode();
}
});
// Apply transition duration and timing to all elements
const transitionDuration = '0.0s';
const transitionTiming = 'ease';
const elementsToTransition = [darkModeToggle, bodyElement];
elementsToTransition.forEach(function(element) {
element.style.transitionDuration = transitionDuration;
element.style.transitionTimingFunction = transitionTiming;
});
// Check theme preference on page load
const savedThemePreference = localStorage.getItem('themePreference');
if (savedThemePreference === 'dark') {
enableDarkMode();
} else {
disableDarkMode();
}
checkThemePreference();
});
</script>
Head Code
Put this in the <head> section of your site.
<!-- 💙 MEMBERSCRIPT #50 HEAD CODE v0.1 💙 CROSS-DEVICE DARK MODE -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const themePreference = localStorage.getItem('themePreference');
if (themePreference === 'dark') {
document.body.classList.add('dark');
}
});
</script>
Body Code
Put this in the </body> section of your site.
<!-- 💙 MEMBERSCRIPT #50 BODY CODE v0.1 💙 CROSS-DEVICE DARK MODE -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const darkModeToggle = document.querySelector('[ms-code-dark-mode="toggle"]');
const bodyElement = document.querySelector('.body');
// Function to check if theme preference is saved in member JSON
function checkThemePreference() {
const memberstack = window.$memberstackDom;
memberstack.getMemberJSON()
.then(function(memberData) {
const themePreference = memberData.data?.themePreference;
if (themePreference === 'dark') {
enableDarkMode();
} else {
disableDarkMode();
}
})
.catch(function(error) {
console.error('Error retrieving member data:', error);
});
}
// Function to enable dark mode
function enableDarkMode() {
darkModeToggle.classList.add('dark');
bodyElement.classList.add('dark');
updateThemePreference('dark');
}
// Function to disable dark mode
function disableDarkMode() {
darkModeToggle.classList.remove('dark');
bodyElement.classList.remove('dark');
updateThemePreference('light');
}
// Function to update theme preference in member JSON
function updateThemePreference(themePreference) {
const memberstack = window.$memberstackDom;
memberstack.getMemberJSON()
.then(function(memberData) {
memberData.data = memberData.data || {};
memberData.data.themePreference = themePreference;
memberstack.updateMemberJSON({ json: memberData.data })
.then(function() {
localStorage.setItem('themePreference', themePreference);
})
.catch(function(error) {
console.error('Error updating member data:', error);
});
})
.catch(function(error) {
console.error('Error retrieving member data:', error);
});
}
// Event listener for dark mode toggle
darkModeToggle.addEventListener('click', function() {
if (darkModeToggle.classList.contains('dark')) {
disableDarkMode();
} else {
enableDarkMode();
}
});
// Apply transition duration and timing to all elements
const transitionDuration = '0.0s';
const transitionTiming = 'ease';
const elementsToTransition = [darkModeToggle, bodyElement];
elementsToTransition.forEach(function(element) {
element.style.transitionDuration = transitionDuration;
element.style.transitionTimingFunction = transitionTiming;
});
// Check theme preference on page load
const savedThemePreference = localStorage.getItem('themePreference');
if (savedThemePreference === 'dark') {
enableDarkMode();
} else {
disableDarkMode();
}
checkThemePreference();
});
</script>

#49 - Disable First Option in a Select Input
Prevent users from selecting the placeholder option in your select inputs.
<!-- 💙 MEMBERSCRIPT #49 v0.1 💙 DISABLE FIRST OPTION IN SELECT INPUT -->
<script>
let selects = document.querySelectorAll("select[ms-code=hide-first-option]");
selects.forEach((select) => {
let options = select.getElementsByTagName("option");
options[0].hidden = true;
});
</script>
<!-- 💙 MEMBERSCRIPT #49 v0.1 💙 DISABLE FIRST OPTION IN SELECT INPUT -->
<script>
let selects = document.querySelectorAll("select[ms-code=hide-first-option]");
selects.forEach((select) => {
let options = select.getElementsByTagName("option");
options[0].hidden = true;
});
</script>

#48 - Autocomplete Address Inputs
Prefill all address inputs using the Google Places API!
Head Code
Place this in your page <head>
<!-- 💙 MEMBERSCRIPT #48 HEAD CODE v0.1 💙 AUTOFILL ADDRESS INPUTS -->
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR-API-KEY&libraries=places&callback=initAutocomplete" async defer> </script>
<style>
.pac-logo::after {
display: none;
}
.pac-container {
border-radius: 5px;
border: 1px solid #ccc;
}
.pac-item {
padding: 0 10px;
}
</style>
Body Code
Place this in your page </body>
<!-- 💙 MEMBERSCRIPT #48 BODY CODE v0.1 💙 AUTOFILL ADDRESS INPUTS -->
<script>
let autocomplete;
function initAutocomplete() {
autocomplete = new google.maps.places.Autocomplete(
document.querySelector('input[ms-code-input="address"]'),
{
componentRestrictions: { country: ['US'] },
fields: ['address_components'],
types: ['address']
}
);
autocomplete.addListener('place_changed', function() {
const place = autocomplete.getPlace();
if (place) {
const addressInput = document.querySelector('input[ms-code-input="address"]');
const cityInput = document.querySelector('input[ms-code-input="city"]');
const regionInput = document.querySelector('input[ms-code-input="region"]');
const countryInput = document.querySelector('input[ms-code-input="country"]');
const postalCodeInput = document.querySelector('input[ms-code-input="postal-code"]');
addressInput.value = extractAddress(place);
cityInput.value = extractCity(place);
regionInput.value = extractRegion(place);
countryInput.value = extractCountry(place);
postalCodeInput.value = extractPostalCode(place);
}
});
}
function extractAddress(place) {
let address = '';
const streetNumber = extractComponent(place, 'street_number');
const route = extractComponent(place, 'route');
if (streetNumber) {
address += streetNumber + ' ';
}
if (route) {
address += route;
}
return address.trim();
}
function extractComponent(place, componentType) {
for (const component of place.address_components) {
if (component.types.includes(componentType)) {
return component.long_name;
}
}
return '';
}
function extractCity(place) {
for (const component of place.address_components) {
if (component.types.includes('locality')) {
return component.long_name;
}
}
return '';
}
function extractRegion(place) {
for (const component of place.address_components) {
if (component.types.includes('administrative_area_level_1')) {
return component.long_name;
}
}
return '';
}
function extractCountry(place) {
for (const component of place.address_components) {
if (component.types.includes('country')) {
return component.long_name;
}
}
return '';
}
function extractPostalCode(place) {
for (const component of place.address_components) {
if (component.types.includes('postal_code')) {
return component.long_name;
}
}
return '';
}
</script>
Head Code
Place this in your page <head>
<!-- 💙 MEMBERSCRIPT #48 HEAD CODE v0.1 💙 AUTOFILL ADDRESS INPUTS -->
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR-API-KEY&libraries=places&callback=initAutocomplete" async defer> </script>
<style>
.pac-logo::after {
display: none;
}
.pac-container {
border-radius: 5px;
border: 1px solid #ccc;
}
.pac-item {
padding: 0 10px;
}
</style>
Body Code
Place this in your page </body>
<!-- 💙 MEMBERSCRIPT #48 BODY CODE v0.1 💙 AUTOFILL ADDRESS INPUTS -->
<script>
let autocomplete;
function initAutocomplete() {
autocomplete = new google.maps.places.Autocomplete(
document.querySelector('input[ms-code-input="address"]'),
{
componentRestrictions: { country: ['US'] },
fields: ['address_components'],
types: ['address']
}
);
autocomplete.addListener('place_changed', function() {
const place = autocomplete.getPlace();
if (place) {
const addressInput = document.querySelector('input[ms-code-input="address"]');
const cityInput = document.querySelector('input[ms-code-input="city"]');
const regionInput = document.querySelector('input[ms-code-input="region"]');
const countryInput = document.querySelector('input[ms-code-input="country"]');
const postalCodeInput = document.querySelector('input[ms-code-input="postal-code"]');
addressInput.value = extractAddress(place);
cityInput.value = extractCity(place);
regionInput.value = extractRegion(place);
countryInput.value = extractCountry(place);
postalCodeInput.value = extractPostalCode(place);
}
});
}
function extractAddress(place) {
let address = '';
const streetNumber = extractComponent(place, 'street_number');
const route = extractComponent(place, 'route');
if (streetNumber) {
address += streetNumber + ' ';
}
if (route) {
address += route;
}
return address.trim();
}
function extractComponent(place, componentType) {
for (const component of place.address_components) {
if (component.types.includes(componentType)) {
return component.long_name;
}
}
return '';
}
function extractCity(place) {
for (const component of place.address_components) {
if (component.types.includes('locality')) {
return component.long_name;
}
}
return '';
}
function extractRegion(place) {
for (const component of place.address_components) {
if (component.types.includes('administrative_area_level_1')) {
return component.long_name;
}
}
return '';
}
function extractCountry(place) {
for (const component of place.address_components) {
if (component.types.includes('country')) {
return component.long_name;
}
}
return '';
}
function extractPostalCode(place) {
for (const component of place.address_components) {
if (component.types.includes('postal_code')) {
return component.long_name;
}
}
return '';
}
</script>

#47 - Display Date From Member JSON
Show members a date - for example, when their plan will expire!
<!-- 💙 MEMBERSCRIPT #47 v0.1 💙 DISPLAY ONE TIME DATE -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
const memberstack = window.$memberstackDom;
const formatDate = function(date) {
const options = { month: 'long', day: 'numeric', year: 'numeric' };
return new Date(date).toLocaleDateString('en-US', options);
// Replace 'en-US' with one of these depending on your locale: en-US, en-GB, en-CA, en-AU, fr-FR, de-DE, es-ES, it-IT, ja-JP, ko-KR, pt-BR, ru-RU, zn-CH, ar-SA
};
const updateTextSpans = async function() {
const member = await memberstack.getMemberJSON();
if (!member.data || !member.data['one-time-date']) {
// Member data or one-time date not available, do nothing
return;
}
const oneTimeDate = formatDate(member.data['one-time-date']);
const textSpans = document.querySelectorAll('[ms-code-display-text="one-time-date"]');
textSpans.forEach(span => {
span.textContent = oneTimeDate;
});
};
updateTextSpans();
});
</script>
<!-- 💙 MEMBERSCRIPT #47 v0.1 💙 DISPLAY ONE TIME DATE -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
const memberstack = window.$memberstackDom;
const formatDate = function(date) {
const options = { month: 'long', day: 'numeric', year: 'numeric' };
return new Date(date).toLocaleDateString('en-US', options);
// Replace 'en-US' with one of these depending on your locale: en-US, en-GB, en-CA, en-AU, fr-FR, de-DE, es-ES, it-IT, ja-JP, ko-KR, pt-BR, ru-RU, zn-CH, ar-SA
};
const updateTextSpans = async function() {
const member = await memberstack.getMemberJSON();
if (!member.data || !member.data['one-time-date']) {
// Member data or one-time date not available, do nothing
return;
}
const oneTimeDate = formatDate(member.data['one-time-date']);
const textSpans = document.querySelectorAll('[ms-code-display-text="one-time-date"]');
textSpans.forEach(span => {
span.textContent = oneTimeDate;
});
};
updateTextSpans();
});
</script>

#46 - Confirm Password
Add a confirm password input to your signup & password reset forms.
<!-- 💙 MEMBERSCRIPT #46 v0.1 💙 CONFIRM PASSWORD INPUT -->
<script>
var password = document.querySelector('[data-ms-member=password]')
, confirm_password = document.querySelector('[ms-code-password=confirm]')
function validatePassword(){
if(password.value != confirm_password.value) {
confirm_password.setCustomValidity("Passwords Don't Match");
confirm_password.classList.add("invalid")
confirm_password.classList.remove("valid")
} else {
confirm_password.setCustomValidity('');
confirm_password.classList.remove("invalid")
confirm_password.classList.add("valid")
}
}
password.onchange = validatePassword;
confirm_password.onkeyup = validatePassword;
</script>
<!-- 💙 MEMBERSCRIPT #46 v0.1 💙 CONFIRM PASSWORD INPUT -->
<script>
var password = document.querySelector('[data-ms-member=password]')
, confirm_password = document.querySelector('[ms-code-password=confirm]')
function validatePassword(){
if(password.value != confirm_password.value) {
confirm_password.setCustomValidity("Passwords Don't Match");
confirm_password.classList.add("invalid")
confirm_password.classList.remove("valid")
} else {
confirm_password.setCustomValidity('');
confirm_password.classList.remove("invalid")
confirm_password.classList.add("valid")
}
}
password.onchange = validatePassword;
confirm_password.onkeyup = validatePassword;
</script>

#45 - Show/Hide Password
Add a show/hide password button to any form with a password input.
<!-- 💙 MEMBERSCRIPT #45 v0.2 💙 SHOW AND HIDE PASSWORD -->
<script>
document.querySelectorAll("[ms-code-password='transform']").forEach(function(button) {
button.addEventListener("click", transform);
});
var isPassword = true;
function transform() {
var passwordInputs = document.querySelectorAll("[data-ms-member='password'], [data-ms-member='new-password'], [data-ms-member='current-password']");
passwordInputs.forEach(function(myInput) {
var inputType = myInput.getAttribute("type");
if (isPassword) {
myInput.setAttribute("type", "text");
} else {
myInput.setAttribute("type", "password");
}
});
isPassword = !isPassword;
}
</script>
<!-- 💙 MEMBERSCRIPT #45 v0.2 💙 SHOW AND HIDE PASSWORD -->
<script>
document.querySelectorAll("[ms-code-password='transform']").forEach(function(button) {
button.addEventListener("click", transform);
});
var isPassword = true;
function transform() {
var passwordInputs = document.querySelectorAll("[data-ms-member='password'], [data-ms-member='new-password'], [data-ms-member='current-password']");
passwordInputs.forEach(function(myInput) {
var inputType = myInput.getAttribute("type");
if (isPassword) {
myInput.setAttribute("type", "text");
} else {
myInput.setAttribute("type", "password");
}
});
isPassword = !isPassword;
}
</script>

#44 - Show Element If Attribute Matches Member ID
Conditionally show elements if they have an attribute matching the members' ID.
<!-- 💙 MEMBERSCRIPT #44 v0.1 💙 SHOW ELEMENT IF ATTRIBUTE MATCHES MEMBER ID -->
<script>
document.addEventListener("DOMContentLoaded", function() {
if (localStorage.getItem("_ms-mem")) {
const memberData = JSON.parse(localStorage.getItem("_ms-mem"));
const memberId = memberData.id;
const elements = document.querySelectorAll("[ms-code-member-id='" + memberId + "']");
elements.forEach(element => {
element.style.display = "block";
});
}
});
</script>
<!-- 💙 MEMBERSCRIPT #44 v0.1 💙 SHOW ELEMENT IF ATTRIBUTE MATCHES MEMBER ID -->
<script>
document.addEventListener("DOMContentLoaded", function() {
if (localStorage.getItem("_ms-mem")) {
const memberData = JSON.parse(localStorage.getItem("_ms-mem"));
const memberId = memberData.id;
const elements = document.querySelectorAll("[ms-code-member-id='" + memberId + "']");
elements.forEach(element => {
element.style.display = "block";
});
}
});
</script>

#43 - Block Scrolling When Modal Is Open
Stop the page from scrolling when someone opens a modal.
<!-- 💙 MEMBERSCRIPT #43 v0.1 💙 BLOCK SCROLLING WHEN MODAL IS OPEN -->
<style>
.no-scroll {
overflow: hidden;
}
</style>
<script>
function isDesktopViewport() {
return window.innerWidth >= 900; // Adjust the breakpoint width as needed
}
const codeBlocks = document.querySelectorAll('[ms-code-block-scroll]');
function handleScrollBlock(event) {
if (isDesktopViewport()) {
document.body.classList.add('no-scroll');
}
}
function handleScrollUnblock(event) {
if (isDesktopViewport()) {
document.body.classList.remove('no-scroll');
}
}
codeBlocks.forEach(codeBlock => {
codeBlock.addEventListener('mouseenter', handleScrollBlock);
codeBlock.addEventListener('mouseleave', handleScrollUnblock);
});
</script>
<!-- 💙 MEMBERSCRIPT #43 v0.1 💙 BLOCK SCROLLING WHEN MODAL IS OPEN -->
<style>
.no-scroll {
overflow: hidden;
}
</style>
<script>
function isDesktopViewport() {
return window.innerWidth >= 900; // Adjust the breakpoint width as needed
}
const codeBlocks = document.querySelectorAll('[ms-code-block-scroll]');
function handleScrollBlock(event) {
if (isDesktopViewport()) {
document.body.classList.add('no-scroll');
}
}
function handleScrollUnblock(event) {
if (isDesktopViewport()) {
document.body.classList.remove('no-scroll');
}
}
codeBlocks.forEach(codeBlock => {
codeBlock.addEventListener('mouseenter', handleScrollBlock);
codeBlock.addEventListener('mouseleave', handleScrollUnblock);
});
</script>

#42 - Image Editor Form Field
Allow people to upload & edit photos, then send them to Google Drive!
Head Code
Place this in your page <head>
<!-- 💙 MEMBERSCRIPT #42 HEAD CODE v0.2 💙 FILE EDITOR FEATURE -->
<link rel="stylesheet" href="https://unpkg.com/filepond@^4/dist/filepond.css" />
<link rel="stylesheet" href="https://unpkg.com/filepond-plugin-image-edit/dist/filepond-plugin-image-edit.css" />
<link rel="stylesheet" href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css" />
Body Code
Place this in your page </body>
<!-- 💙 MEMBERSCRIPT #42 BODY CODE v0.2 💙 FILE EDITOR FEATURE -->
<script> src="https://unpkg.com/filepond-plugin-file-encode/dist/filepond-plugin-file-encode.js"> </script>
<script> src="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.js"> </script>
<script> src="https://unpkg.com/filepond-plugin-image-edit/dist/filepond-plugin-image-edit.js"> </script>
<script> src="https://unpkg.com/filepond@^4/dist/filepond.js"> </script>
<script> src="https://scaleflex.cloudimg.io/v7/plugins/filerobot-image-editor/latest/filerobot-image-editor.min.js"> </script>
<style>
.dXhZSB {
background-color: #2962ff;
}
.FIE_root * {
font-family: inherit !important;
}
.SfxModal-Wrapper * {
font-family: inherit !important;
}
.jpHEiD {
font-family: inherit !important;
}
#editor_container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 999;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Register the plugins
FilePond.registerPlugin(FilePondPluginImagePreview);
FilePond.registerPlugin(FilePondPluginImageEdit);
const inputElement = document.querySelector('input[type="file"]');
const pond = FilePond.create(inputElement, {
credits: false,
name: 'fileToUpload',
storeAsFile: true,
imageEditEditor: {
open: (file, instructions) => {
console.log('Open editor', file, instructions);
openFilerobotImageEditor(file, instructions);
},
onconfirm: (output) => {
console.log('Confirm editor', output);
handleImageEditConfirm(output);
},
oncancel: () => {
console.log('Cancel editor');
handleImageEditCancel();
},
onclose: () => {
console.log('Close editor');
handleImageEditClose();
}
}
});
function openFilerobotImageEditor(file, instructions) {
const imageURL = URL.createObjectURL(file);
const config = {
source: imageURL,
onSave: (updatedImage) => {
confirmCallback(updatedImage);
},
annotationsCommon: {
fill: '#ff0000'
},
Text: {
text: 'Add your text here',
font: 'inherit'
}, // Set font to inherit from the page body
Rotate: {
angle: instructions.rotation,
componentType: 'slider'
},
tabsIds: [
'Adjust',
'Annotate',
'Watermark'
],
defaultTabId: 'Annotate',
defaultToolId: 'Text'
};
const editorContainer = document.createElement('div');
editorContainer.id = 'editor_container';
document.body.appendChild(editorContainer);
const filerobotImageEditor = new window.FilerobotImageEditor(editorContainer, config);
const confirmCallback = (output) => {
console.log('Confirmed:', output);
const dataURL = output.imageBase64;
const file = dataURLToFile(dataURL, output.name);
// Add the file to FilePond
pond.addFiles([file]);
document.body.removeChild(editorContainer); // Remove the editor container
};
function dataURLToFile(dataURL, fileName) {
const arr = dataURL.split(',');
const mime = arr[0].match(/:(.*?);/)[1];
const fileExtension = mime.split('/')[1];
const updatedFileName = fileName + '.' + fileExtension;
const bstr = atob(arr[1]);
const n = bstr.length;
const u8arr = new Uint8Array(n);
for (let i = 0; i < n; i++) {
u8arr[i] = bstr.charCodeAt(i);
}
return new File([u8arr], updatedFileName, { type: mime });
}
const cancelCallback = () => {
console.log('Canceled');
document.body.removeChild(editorContainer); // Remove the editor container
};
const closeButton = document.createElement('button');
closeButton.textContent = 'Close';
closeButton.addEventListener('click', () => {
filerobotImageEditor.onClose();
});
const buttonContainer = document.createElement('div');
buttonContainer.appendChild(closeButton);
editorContainer.appendChild(buttonContainer);
filerobotImageEditor.render({
onClose: (closingReason) => {
console.log('Closing reason', closingReason);
filerobotImageEditor.terminate();
},
});
}
function handleImageEditConfirm(output) {
console.log('Image edit confirmed:', output);
// Handle the confirmed output here
}
function handleImageEditCancel() {
console.log('Image edit canceled');
// Handle the canceled edit here
}
function handleImageEditClose() {
console.log('Image editor closed');
// Handle the editor close here
}
});
</script>
Head Code
Place this in your page <head>
<!-- 💙 MEMBERSCRIPT #42 HEAD CODE v0.2 💙 FILE EDITOR FEATURE -->
<link rel="stylesheet" href="https://unpkg.com/filepond@^4/dist/filepond.css" />
<link rel="stylesheet" href="https://unpkg.com/filepond-plugin-image-edit/dist/filepond-plugin-image-edit.css" />
<link rel="stylesheet" href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css" />
Body Code
Place this in your page </body>
<!-- 💙 MEMBERSCRIPT #42 BODY CODE v0.2 💙 FILE EDITOR FEATURE -->
<script> src="https://unpkg.com/filepond-plugin-file-encode/dist/filepond-plugin-file-encode.js"> </script>
<script> src="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.js"> </script>
<script> src="https://unpkg.com/filepond-plugin-image-edit/dist/filepond-plugin-image-edit.js"> </script>
<script> src="https://unpkg.com/filepond@^4/dist/filepond.js"> </script>
<script> src="https://scaleflex.cloudimg.io/v7/plugins/filerobot-image-editor/latest/filerobot-image-editor.min.js"> </script>
<style>
.dXhZSB {
background-color: #2962ff;
}
.FIE_root * {
font-family: inherit !important;
}
.SfxModal-Wrapper * {
font-family: inherit !important;
}
.jpHEiD {
font-family: inherit !important;
}
#editor_container {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 999;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Register the plugins
FilePond.registerPlugin(FilePondPluginImagePreview);
FilePond.registerPlugin(FilePondPluginImageEdit);
const inputElement = document.querySelector('input[type="file"]');
const pond = FilePond.create(inputElement, {
credits: false,
name: 'fileToUpload',
storeAsFile: true,
imageEditEditor: {
open: (file, instructions) => {
console.log('Open editor', file, instructions);
openFilerobotImageEditor(file, instructions);
},
onconfirm: (output) => {
console.log('Confirm editor', output);
handleImageEditConfirm(output);
},
oncancel: () => {
console.log('Cancel editor');
handleImageEditCancel();
},
onclose: () => {
console.log('Close editor');
handleImageEditClose();
}
}
});
function openFilerobotImageEditor(file, instructions) {
const imageURL = URL.createObjectURL(file);
const config = {
source: imageURL,
onSave: (updatedImage) => {
confirmCallback(updatedImage);
},
annotationsCommon: {
fill: '#ff0000'
},
Text: {
text: 'Add your text here',
font: 'inherit'
}, // Set font to inherit from the page body
Rotate: {
angle: instructions.rotation,
componentType: 'slider'
},
tabsIds: [
'Adjust',
'Annotate',
'Watermark'
],
defaultTabId: 'Annotate',
defaultToolId: 'Text'
};
const editorContainer = document.createElement('div');
editorContainer.id = 'editor_container';
document.body.appendChild(editorContainer);
const filerobotImageEditor = new window.FilerobotImageEditor(editorContainer, config);
const confirmCallback = (output) => {
console.log('Confirmed:', output);
const dataURL = output.imageBase64;
const file = dataURLToFile(dataURL, output.name);
// Add the file to FilePond
pond.addFiles([file]);
document.body.removeChild(editorContainer); // Remove the editor container
};
function dataURLToFile(dataURL, fileName) {
const arr = dataURL.split(',');
const mime = arr[0].match(/:(.*?);/)[1];
const fileExtension = mime.split('/')[1];
const updatedFileName = fileName + '.' + fileExtension;
const bstr = atob(arr[1]);
const n = bstr.length;
const u8arr = new Uint8Array(n);
for (let i = 0; i < n; i++) {
u8arr[i] = bstr.charCodeAt(i);
}
return new File([u8arr], updatedFileName, { type: mime });
}
const cancelCallback = () => {
console.log('Canceled');
document.body.removeChild(editorContainer); // Remove the editor container
};
const closeButton = document.createElement('button');
closeButton.textContent = 'Close';
closeButton.addEventListener('click', () => {
filerobotImageEditor.onClose();
});
const buttonContainer = document.createElement('div');
buttonContainer.appendChild(closeButton);
editorContainer.appendChild(buttonContainer);
filerobotImageEditor.render({
onClose: (closingReason) => {
console.log('Closing reason', closingReason);
filerobotImageEditor.terminate();
},
});
}
function handleImageEditConfirm(output) {
console.log('Image edit confirmed:', output);
// Handle the confirmed output here
}
function handleImageEditCancel() {
console.log('Image edit canceled');
// Handle the canceled edit here
}
function handleImageEditClose() {
console.log('Image editor closed');
// Handle the editor close here
}
});
</script>

#41 - Perfect Phone Number Inputs
International phone number inputs, the way they should be.
With IP Lookup
Use this if you want the users' IP country to automatically be prefilled. IMPORTANT: Do not use this with profile forms or it will behave erratically.
<!-- 💙 MEMBERSCRIPT #41 v0.2 💙 PERFECT PHONE NUMBER INPUTS (WITH IP LOOKUP) -->
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/css/intlTelInput.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/intlTelInput.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"> </script>
<script>
$(document).ready(function() {
$('input[ms-code-phone-number]').each(function() {
var input = this;
var preferredCountries = $(input).attr('ms-code-phone-number').split(',');
var iti = window.intlTelInput(input, {
preferredCountries: preferredCountries,
utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"
});
$.get("https://ipinfo.io", function(response) {
var countryCode = response.country;
iti.setCountry(countryCode);
}, "jsonp");
input.addEventListener('change', formatPhoneNumber);
input.addEventListener('keyup', formatPhoneNumber);
function formatPhoneNumber() {
var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
input.value = formattedNumber;
}
var form = $(input).closest('form');
form.submit(function() {
var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
input.value = formattedNumber;
});
});
});
</script>
Without IP Lookup
Use this on profile forms and/or if you do not want to automatically prefill based on user IP.
<!-- 💙 MEMBERSCRIPT #41 v0.2 💙 PERFECT PHONE NUMBER INPUTS (WITHOUT IP LOOKUP) -->
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/css/intlTelInput.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/intlTelInput.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"> </script>
<script>
$(document).ready(function() {
$('input[ms-code-phone-number]').each(function() {
var input = this;
var preferredCountries = $(input).attr('ms-code-phone-number').split(',');
var iti = window.intlTelInput(input, {
preferredCountries: preferredCountries,
utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"
});
input.addEventListener('change', formatPhoneNumber);
input.addEventListener('keyup', formatPhoneNumber);
function formatPhoneNumber() {
var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
input.value = formattedNumber;
}
var form = $(input).closest('form');
form.submit(function() {
var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
input.value = formattedNumber;
});
});
});
</script>
With IP Lookup
Use this if you want the users' IP country to automatically be prefilled. IMPORTANT: Do not use this with profile forms or it will behave erratically.
<!-- 💙 MEMBERSCRIPT #41 v0.2 💙 PERFECT PHONE NUMBER INPUTS (WITH IP LOOKUP) -->
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/css/intlTelInput.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/intlTelInput.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"> </script>
<script>
$(document).ready(function() {
$('input[ms-code-phone-number]').each(function() {
var input = this;
var preferredCountries = $(input).attr('ms-code-phone-number').split(',');
var iti = window.intlTelInput(input, {
preferredCountries: preferredCountries,
utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"
});
$.get("https://ipinfo.io", function(response) {
var countryCode = response.country;
iti.setCountry(countryCode);
}, "jsonp");
input.addEventListener('change', formatPhoneNumber);
input.addEventListener('keyup', formatPhoneNumber);
function formatPhoneNumber() {
var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
input.value = formattedNumber;
}
var form = $(input).closest('form');
form.submit(function() {
var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
input.value = formattedNumber;
});
});
});
</script>
Without IP Lookup
Use this on profile forms and/or if you do not want to automatically prefill based on user IP.
<!-- 💙 MEMBERSCRIPT #41 v0.2 💙 PERFECT PHONE NUMBER INPUTS (WITHOUT IP LOOKUP) -->
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/css/intlTelInput.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/intlTelInput.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"> </script>
<script>
$(document).ready(function() {
$('input[ms-code-phone-number]').each(function() {
var input = this;
var preferredCountries = $(input).attr('ms-code-phone-number').split(',');
var iti = window.intlTelInput(input, {
preferredCountries: preferredCountries,
utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"
});
input.addEventListener('change', formatPhoneNumber);
input.addEventListener('keyup', formatPhoneNumber);
function formatPhoneNumber() {
var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
input.value = formattedNumber;
}
var form = $(input).closest('form');
form.submit(function() {
var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
input.value = formattedNumber;
});
});
});
</script>

#40 - Drag And Drop File Uploader
Easily add a drag n' drop file upload feature to your Webflow site!
Important
If you are using MemberScript #38, make sure you put this script AFTER!
<!-- 💙 MEMBERSCRIPT #40 v0.1 💙 DRAG AND DROP FILE UPLOADER -->
<script> src="https://unpkg.com/filepond@^4/dist/filepond.js"> </script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const inputElement = document.querySelector('input[type="file"]');
const pond = FilePond.create(inputElement, {
credits: false,
name: 'fileToUpload',
storeAsFile: true
// for more property options, go to https://pqina.nl/filepond/docs/api/instance/properties/
});
});
</script>
Important
If you are using MemberScript #38, make sure you put this script AFTER!
<!-- 💙 MEMBERSCRIPT #40 v0.1 💙 DRAG AND DROP FILE UPLOADER -->
<script> src="https://unpkg.com/filepond@^4/dist/filepond.js"> </script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const inputElement = document.querySelector('input[type="file"]');
const pond = FilePond.create(inputElement, {
credits: false,
name: 'fileToUpload',
storeAsFile: true
// for more property options, go to https://pqina.nl/filepond/docs/api/instance/properties/
});
});
</script>

#39 - Better Select Fields
Add searches and a better UI to select & multi-select fields!
Head Code
Put this in the <head> section of your page.
<!-- 💙 MEMBERSCRIPT #39 v0.1 HEAD CODE 💙 BETTER SELECT FIELDS -->
<script src="https://code.jquery.com/jquery-3.7.0.min.js" integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous"> </script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" />
Body Code
Put this in the </body> section of your page.
<!-- 💙 MEMBERSCRIPT #39 v0.1 BODY CODE 💙 BETTER SELECT FIELDS -->
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"> </script>
<script>
$(document).ready(function() {
$('[ms-code-custom-select="select-with-search"]').select2();
});
</script>
Head Code
Put this in the <head> section of your page.
<!-- 💙 MEMBERSCRIPT #39 v0.1 HEAD CODE 💙 BETTER SELECT FIELDS -->
<script src="https://code.jquery.com/jquery-3.7.0.min.js" integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous"> </script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" />
Body Code
Put this in the </body> section of your page.
<!-- 💙 MEMBERSCRIPT #39 v0.1 BODY CODE 💙 BETTER SELECT FIELDS -->
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"> </script>
<script>
$(document).ready(function() {
$('[ms-code-custom-select="select-with-search"]').select2();
});
</script>
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 SlackExplore 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.
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.