How to validate a form before submitting
Short answer
Attributes like required, minlength and type="email" already check the fields. The browser does not submit the form until they are filled correctly:
<form id="form">
<input name="name" required minlength="2" placeholder="Name">
<input name="email" type="email" required placeholder="Email">
<input name="age" type="number" min="18" max="120" placeholder="Age">
<button>Send</button>
</form>
<p id="out"></p>
<script>
document.getElementById('form').addEventListener('submit', (event) => {
event.preventDefault();
document.getElementById('out').textContent = 'All fields are valid';
});
</script>Custom rules
setCustomValidity() adds your own check. A non-empty message makes the field invalid, an empty string makes it valid again:
const password = form.elements.password;
const repeat = form.elements.repeat;
repeat.addEventListener('input', () => {
repeat.setCustomValidity(repeat.value === password.value ? '' : 'Passwords do not match');
});Your own error output
The novalidate attribute on the form turns off the browser messages, and you show the errors yourself, for example in elements with the class error and a data-for attribute with the field name:
form.addEventListener('submit', (event) => {
if (!form.checkValidity()) {
event.preventDefault();
for (const field of form.querySelectorAll('input')) {
const error = form.querySelector(`.error[data-for="${field.name}"]`);
if (error) error.textContent = field.validationMessage;
}
}
});The same rules without a browser
Checks written as plain functions can be reused anywhere, including on the server:
const validate = (data) => {
const errors = {};
if (data.name.trim().length < 2) errors.name = 'Name is too short';
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) errors.email = 'Invalid email';
return errors;
};
console.log(validate({ name: 'A', email: 'anna@example.com' }));
console.log(Object.keys(validate({ name: 'Anna', email: 'anna@example.com' })).length);Output:
{
"name": "Name is too short"
}
0Common mistake
Checks in the browser are only for convenience. Anyone can send data to the server bypassing the form, so the server must check everything again.
// a request without any form at all
await fetch('/api/signup', { method: 'POST', body: JSON.stringify({ email: 'not-an-email' }) });Functions used in this recipe
- RegExp.prototype.test — Checks whether a string matches a pattern.
- Object.keys — Returns an array of an object's property names.