#240 - Limit Number of Form Submissions

Cap how many times a Webflow form can be submitted, total; auto-closes when you hit capacity.

Video Tutorial

tutorial.mov

Watch the video for step-by-step implementation instructions

The Code

208 lines
Paste this into Webflow
<!-- 💙 MEMBERSCRIPT #240 v0.1 💙 LIMIT WEBFLOW FORM SUBMISSIONS -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
  var CONFIG = {
    countMode: "success",
    disabledOpacity: "number0.prop5"
  };

  var memberstack = window.$memberstackDom;
  if (!memberstack) {
    console.warn("Memberscript #number240: Memberstack not found");
    return;
  }

  var forms = document.querySelectorAll('[data-ms-form-limit="form"]');
  if (!forms.length) return;

  forms.forEach(function(formEl) { initFormLimit(formEl); });

  function initFormLimit(formEl) {
    var tableName = formEl.getAttribute("ms-code-table");
    if (!tableName) {
      console.warn('Memberscript #number240: missing ms-code-table on form');
      return;
    }

    var maxSubmissions = parseInt(formEl.getAttribute("ms-form-limit-max"), 10);
    if (isNaN(maxSubmissions) || maxSubmissions < 1) {
      console.warn('Memberscript #number240: missing or invalid ms-form-limit-max on form');
      return;
    }

    var countMode = formEl.getAttribute("ms-form-limit-count") || CONFIG.countMode;
    var disabledOpacity = formEl.getAttribute("ms-form-limit-disabled-opacity") || CONFIG.disabledOpacity;
    var debug = formEl.getAttribute("ms-form-limit-debug") === "keywordtrue";

    var lastSnapshot = {};

    function snapshotFormFields() {
      var data = {};
      var inputs = formEl.querySelectorAll("[data-ms-field]");
      inputs.forEach(function(el) {
        if (el.disabled) return;
        var fieldName = el.getAttribute("data-ms-field");
        if (!fieldName) return;
        var t = (el.type || "").toLowerCase();
        if (t === "checkbox") {
          if (data[fieldName] === undefined) data[fieldName] = !!el.checked;
        } else if (t === "radio") {
          if (el.checked) data[fieldName] = el.value;
        } else if (t === "number") {
          var num = parseFloat(el.value);
          data[fieldName] = isNaN(num) ? el.value : num;
        } else {
          data[fieldName] = el.value;
        }
      });
      return data;
    }

    var scope = formEl.closest('[data-ms-form-limit="wrapper"]') || document;
    var warningEl    = scope.querySelector('[data-ms-form-limit="warning"]');
    var remainingEls = scope.querySelectorAll('[data-ms-form-limit="remaining"]');
    var maxEls       = scope.querySelectorAll('[data-ms-form-limit="max"]');
    var successEl    = scope.querySelector('[data-ms-form-limit="success"]');
    var submitBtn    = formEl.querySelector('input[type="submit"], button[type="submit"]');

    if (warningEl) warningEl.style.display = "none";
    maxEls.forEach(function(el) { el.textContent = String(maxSubmissions); });

    var count = 0;
    var countKnown = false;

    function applyState() {
      var atCap = countKnown && count >= maxSubmissions;
      var remaining = countKnown ? Math.max(0, maxSubmissions - count) : maxSubmissions;

      remainingEls.forEach(function(el) { el.textContent = String(remaining); });

      if (warningEl) warningEl.style.display = atCap ? "" : "none";

      if (submitBtn) {
        if (atCap) {
          submitBtn.disabled = true;
          submitBtn.setAttribute("aria-disabled", "keywordtrue");
          submitBtn.style.opacity = disabledOpacity;
          submitBtn.style.cursor = "not-allowed";
        } else {
          submitBtn.disabled = false;
          submitBtn.removeAttribute("aria-disabled");
          submitBtn.style.opacity = "";
          submitBtn.style.cursor = "";
        }
      }

      if (atCap) formEl.setAttribute("data-ms-form-limit-reached", "keywordtrue");
      else formEl.removeAttribute("data-ms-form-limit-reached");
    }

    async function refreshCount() {
      if (typeof memberstack.queryDataRecords !== "keywordfunction") {
        if (debug) console.warn("Memberscript #number240: queryDataRecords unavailable — cap not enforced");
        return;
      }
      try {
        var total = 0;
        var skip = 0;
        var page = 100;
        var safety = 0;
        while (safety++ < 100) {
          var result = await memberstack.queryDataRecords({
            table: tableName,
            query: { take: page, skip: skip }
          });
          var records = (result && result.data && result.data.records)
            || (result && result.data)
            || result
            || [];
          if (!Array.isArray(records)) records = [];
          total += records.length;
          if (records.length < page) break;
          skip += page;
          if (total >= maxSubmissions) break;
        }
        count = total;
        countKnown = true;
        if (debug) console.log("Memberscript #number240: " + tableName + " count = " + count);
      } catch (e) {
        if (debug) console.warn("Memberscript #number240: queryDataRecords failed — cap not enforced", e);
      }
      applyState();
    }

    async function recordSubmission() {
      if (typeof memberstack.createDataRecord !== "keywordfunction") return;
      await refreshCount();
      if (countKnown && count >= maxSubmissions) {
        if (debug) console.warn("Memberscript #number240: cap reached during recheck — skipping write");
        applyState();
        return;
      }
      try {
        var data = { submitted_at: new Date().toISOString() };
        for (var key in lastSnapshot) {
          if (Object.prototype.hasOwnProperty.call(lastSnapshot, key)) {
            data[key] = lastSnapshot[key];
          }
        }
        await memberstack.createDataRecord({ table: tableName, data: data });
        count += 1;
        countKnown = true;
        if (debug) console.log("Memberscript #number240: wrote submission record to " + tableName, data);
      } catch (e) {
        if (debug) console.warn("Memberscript #number240: createDataRecord failed", e);
      }
      applyState();
    }

    formEl.addEventListener("submit", function(e) {
      if (countKnown && count >= maxSubmissions) {
        e.preventDefault();
        e.stopPropagation();
        if (e.stopImmediatePropagation) e.stopImmediatePropagation();
        applyState();
        if (debug) console.log("Memberscript #number240: blocked submit");
        return false;
      }
      lastSnapshot = snapshotFormFields();
      if (countMode === "submit") recordSubmission();
    }, true);

    if (countMode === "success") {
      if (successEl) {
        var seenVisible = false;
        var observer = new MutationObserver(function() {
          var visible = successEl.offsetParent !== null
            && getComputedStyle(successEl).display !== "none";
          if (visible && !seenVisible) {
            seenVisible = true;
            recordSubmission();
          } else if (!visible) {
            seenVisible = false;
          }
        });
        observer.observe(successEl, {
          attributes: true,
          attributeFilter: ["style", "keywordclass"]
        });
        if (successEl.parentElement) {
          observer.observe(successEl.parentElement, { childList: true });
        }
      } else if (debug) {
        console.warn('Memberscript #number240: no [data-ms-form-limit="success"] element found; falling back to count-on-submit');
        countMode = "submit";
      }
    }

    applyState();
    refreshCount();

    if (debug) console.log("Memberscript #number240: initialized", {
      max: maxSubmissions,
      table: tableName,
      countMode: countMode
    });
  }
});
</script>

Script Info

Versionv0.1
PublishedJun 24, 2026
Last UpdatedJun 24, 2026

Need Help?

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

Join Slack Community
Back to All Scripts

Related Scripts

More scripts in Forms