After a string of support tickets that traced back to malformed data slipping past a form and into a client's database, we put together a short internal checklist for form validation that now goes on every project regardless of framework:
- Validate on blur, not just on submit, so users get feedback while they are still in the field rather than being surprised by a wall of errors after filling out an entire long form
- Never rely on client-side validation alone — every rule is re-checked on the server, since client-side JavaScript can always be bypassed, disabled, or simply not run in time before a request goes out
- Disable the submit button while a request is in flight, to stop duplicate submissions from an impatient double-click, which was directly responsible for more than one of the "duplicate order" tickets that prompted this checklist in the first place
- Show one clear message per invalid field, not a single generic banner at the top that forces the user to hunt through the form to figure out what actually needs fixing
- Trim whitespace on text inputs before validating, since a stray space is an easy way to fail an otherwise valid email, and it is one of the most common causes of a "but I typed it correctly" support ticket
- Format phone numbers and other structured input as the user types where reasonable, rather than only validating the format after the fact and rejecting a value the user had no visual cue was wrong
Why we wrote this down instead of just knowing it
None of this is exotic, and every developer on the team would have told you most of these rules if you asked in isolation. The problem was never knowledge, it was consistency — one project's contact form trimmed whitespace and another's did not, purely because two different developers built them a year apart with no shared reference to work from, and each one had their own slightly different opinion about what "good enough" validation looked like on a given day. A checklist turns an implicit shared understanding into something that gets checked the same way on every project, regardless of who is building it or how much time pressure they are under.
The specific incidents that shaped each rule
A few of these rules exist because of a specific, memorable failure rather than abstract best practice. The duplicate-submission rule traces back to a client's registration form that, under a slow connection, let users click submit two or three times while waiting for a response, each click firing a separate request and creating duplicate customer records that then had to be manually merged. The whitespace-trimming rule came from a client complaint that their email "wasn't working" on a login form, which turned out to be a trailing space from an autocomplete suggestion on their phone's keyboard, invisible in the input field but enough to fail an exact-match lookup against the stored email address.
Server-side validation is not optional, ever
We list "never rely on client-side validation alone" not because any developer on the team genuinely believes otherwise in principle, but because it is the rule most likely to get skipped under deadline pressure on a form that "obviously" only real users will ever submit through the browser. Every field validated on the client gets the identical rule re-checked server-side, using a shared validation schema definition where the framework allows it, specifically so the two checks cannot drift apart the way the client-only and server-only logic in older projects sometimes did.
Accessibility considerations we folded into the same checklist
Since we were already standardizing form behavior, we used the opportunity to bake in a few accessibility basics at the same time rather than treating them as a separate pass: error messages are associated with their field using `aria-describedby` so screen reader users hear the specific error rather than just a generic "form has errors" announcement, and focus moves to the first invalid field on a failed submit rather than leaving a keyboard user stranded at the submit button with no indication of what needs fixing.
What changed after adopting it
Writing it down as a checklist, and adding it to our project kickoff template so it gets referenced explicitly rather than assumed, has meant fewer of these bugs reaching production in the first place. We also started adding a short automated test for each of these rules on any form handling library we build internally, so a regression gets caught before code review rather than after a client reports it. The checklist itself is short enough to read in under a minute, which was deliberate — a validation checklist nobody actually reads during a busy sprint does not help anyone, and the whole point of writing it down was to make the right behavior the path of least resistance rather than one more thing to remember from scratch on every new project.
Building a shared validation module instead of repeating the checklist by hand
Beyond the checklist itself, we extracted the actual implementation into a small internal JavaScript module that any project can pull in, covering the common field types — email, phone, required text, numeric ranges — with the checklist's rules already baked in rather than re-implemented by hand on every form. This matters more than it might sound: a checklist tells a developer what to do, but a shared module removes the opportunity to quietly skip a step under time pressure, since using the module at all means getting the whitespace trimming and the blur-triggered validation for free without any extra effort. Projects using the shared module have had noticeably fewer validation-related bugs reported than the handful of older projects still running bespoke, pre-checklist validation code.
Handling validation error messages across different languages
A couple of our clients serve audiences in more than one language, and we learned the hard way that hardcoding English error messages inside a shared validation module creates its own maintenance headache once a client needs the same form translated. The shared module now accepts a small message dictionary as configuration rather than hardcoding any user-facing text internally, which took a bit more upfront design work but has already paid off on two multilingual client projects where translated error messages needed to match the surrounding page's language without any special-casing in the validation logic itself.
What we still get wrong occasionally
We would be overstating the checklist's success if we implied it has eliminated every validation bug. Async validation — checking whether a username is already taken, for instance — is not fully covered by the current checklist, and it introduces its own timing complexity (debouncing the check, handling a slow response arriving after the user has already changed the field again) that we are still working out a standard pattern for. We expect to add a dedicated section to the checklist covering async validation once we have settled on an approach we are confident enough in to call a standard, rather than documenting something we ourselves are still actively figuring out.
Measuring whether the checklist actually worked
Rather than assume the checklist helped just because it felt like the right thing to do, we went back through our support ticket history a few months after rolling it out and tagged tickets that traced back to a validation gap the checklist would have caught. The count dropped noticeably compared to the same period the previous year, though we are careful about reading too much precision into that comparison since overall client traffic and project count also changed in that window. Directionally, though, it lines up with what the team's own sense on the ground has been: fewer of the small, avoidable "the form let bad data through" tickets that used to eat an afternoon here and there across the support rotation.
Extending the checklist to non-form input eventually
A natural next step we are considering, though we have not committed to it yet, is extending the same discipline to other places user input enters a system outside of traditional forms — URL query parameters driving a filtered view, for instance, or data pasted into a rich text editor. These inputs share the same fundamental risk as form fields but do not currently get the same standardized treatment, and a few smaller bugs this year have made the case that they probably should. For now the checklist stays scoped to forms specifically, since trying to solve every input-validation problem in one pass risked producing a document too broad for anyone to actually internalize, but it is on our list to revisit once the current version has had more time to prove itself in practice.