Intermediate9 min

Form Validation Best Practices

Learn the best practices for validating form data on both the client and server side. Improve user experience, reduce errors, and keep your data clean.

Prerequisites

  • An HTML form on your website
  • A FormsList account (free) for server-side validation
  • Basic JavaScript knowledge
1

Use HTML5 built-in validation

Start with native HTML5 validation attributes. They provide instant feedback without any JavaScript and work across all modern browsers. Use required, type, minlength, maxlength, and pattern attributes to enforce basic rules directly in the markup.

<form action="https://formslist.com/f/YOUR_FORM_HASH" method="POST">
  <label for="name">Name (2-50 characters)</label>
  <input type="text" id="name" name="name" required minlength="2" maxlength="50" />

  <label for="email">Email</label>
  <input type="email" id="email" name="email" required />

  <label for="phone">Phone (optional, US format)</label>
  <input type="tel" id="phone" name="phone" pattern="\(\d{3}\)\s?\d{3}-\d{4}" placeholder="(555) 123-4567" />

  <label for="website">Website</label>
  <input type="url" id="website" name="website" placeholder="https://example.com" />

  <button type="submit">Submit</button>
</form>
2

Add real-time JavaScript validation

Enhance the experience by showing validation errors as the user types or moves between fields. Listen for the input and blur events, validate the value, and display an error message next to the field. This catches mistakes before the user hits submit.

<script>
  const form = document.querySelector("form");
  const fields = form.querySelectorAll("input, textarea");

  function showError(field, message) {
    let errorEl = field.nextElementSibling;
    if (!errorEl || !errorEl.classList.contains("error-msg")) {
      errorEl = document.createElement("span");
      errorEl.classList.add("error-msg");
      errorEl.style.color = "red";
      errorEl.style.fontSize = "0.85rem";
      field.after(errorEl);
    }
    errorEl.textContent = message;
  }

  function clearError(field) {
    const errorEl = field.nextElementSibling;
    if (errorEl && errorEl.classList.contains("error-msg")) {
      errorEl.textContent = "";
    }
  }

  fields.forEach((field) => {
    field.addEventListener("blur", () => {
      if (!field.checkValidity()) {
        showError(field, field.validationMessage);
      } else {
        clearError(field);
      }
    });

    field.addEventListener("input", () => {
      if (field.validity.valid) {
        clearError(field);
      }
    });
  });
</script>
3

Display clear error messages

Replace generic browser validation messages with custom, user-friendly text. Each error should tell the user exactly what is wrong and how to fix it. Place errors directly below the relevant field and use a consistent visual style (red text or a red border).

<style>
  input:invalid:not(:placeholder-shown) {
    border-color: #ef4444;
  }
  input:valid:not(:placeholder-shown) {
    border-color: #22c55e;
  }
  .error-msg {
    display: block;
    margin-top: 0.25rem;
    color: #ef4444;
    font-size: 0.85rem;
  }
</style>

<script>
  const customMessages = {
    name: "Please enter your full name (at least 2 characters).",
    email: "Please enter a valid email address (e.g., you@example.com).",
    phone: "Please enter a US phone number: (555) 123-4567.",
  };

  fields.forEach((field) => {
    field.addEventListener("invalid", (e) => {
      e.preventDefault();
      const message = customMessages[field.name] || field.validationMessage;
      showError(field, message);
    });
  });
</script>
4

Validate on the server side

Never rely on client-side validation alone — it can be bypassed. Always validate on the server. FormsList performs server-side validation automatically, checking field types, required fields, and spam patterns. If you use your own backend, validate every field again before processing.

// Example server-side validation (Node.js / Express)
app.post("/submit", (req, res) => {
  const { name, email, message } = req.body;
  const errors = [];

  if (!name || name.trim().length < 2) {
    errors.push("Name must be at least 2 characters.");
  }
  if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
    errors.push("A valid email address is required.");
  }
  if (!message || message.trim().length < 10) {
    errors.push("Message must be at least 10 characters.");
  }

  if (errors.length > 0) {
    return res.status(400).json({ errors });
  }

  // Process the valid submission...
  res.json({ success: true });
});
5

Handle validation UX edge cases

Do not show errors before the user has interacted with a field. Disable the submit button while the form is submitting to prevent duplicates. After a successful submission, clear the form and show a success message. If the server returns errors, map them back to the relevant fields.

<script>
  const submitBtn = form.querySelector("button[type='submit']");

  form.addEventListener("submit", async (e) => {
    e.preventDefault();

    // Prevent double submission
    submitBtn.disabled = true;
    submitBtn.textContent = "Sending...";

    const data = new FormData(form);

    try {
      const res = await fetch("https://formslist.com/f/YOUR_FORM_HASH", {
        method: "POST",
        body: data,
      });

      if (res.ok) {
        form.reset();
        form.insertAdjacentHTML(
          "afterend",
          '<p style="color:green">Thank you! Your message has been sent.</p>'
        );
      } else {
        const result = await res.json();
        // Show server-side errors
        alert(result.errors ? result.errors.join("\n") : "Something went wrong.");
      }
    } catch {
      alert("Network error. Please try again.");
    } finally {
      submitBtn.disabled = false;
      submitBtn.textContent = "Submit";
    }
  });
</script>

Frequently Asked Questions

Ready to collect form submissions?

Set up your form backend in under a minute. No server required, no complex configuration — just a simple endpoint for your forms.