How to engineer secure, user-friendly, bot-resistant form inputs
Form validation is a critical part of every web application, impacting user experience, data quality, and security.
Collecting user input is crucial in many email flows, including:
The key is to balance usability with security. Let's look at some key tips for creating validated form inputs that are robust, accessible, and resistant to common attacks, all while still allowing legitimate users to complete forms smoothly.
Any required fields should include the required
attribute. Adding the required attribute provides a basic level of client-side validation and maintains best accessibility practices.
<input type="email" required />
You should also visually identify any required field so your users can immediately identify required information, like adding an asterisk or a label with a required indicator on an email input field for a newsletter subscription.
HTML5 offers a wide range of input types that include built-in validation and are accessible by default. As a starting point, choose the input type that best fits your need (e.g., email, number, date, tel, etc.). Always supplement these with clear label elements for accessibility and context.
Inputs also include attributes that can help you validate inputs (like maxlength
, min
, max
, or step
). Of special interest for collecting emails is the pattern
attribute. It provides a regular expression the input must pass to be accepted and works with these input types: text
, search
, url
, tel
, email
, and password
.
<input type="email" pattern="^\S+@\S+$" />
This pattern ensures that the input contains a non-whitespace string, followed by "@", followed by a non-whitespace string. Be careful of being too restrictive with the pattern, as it may prevent users from entering valid email addresses.
While helpful, these validation attributes only cover basic use-cases and only client-side.
After you properly implement HTML input types and attributes, use a library for more precise validation. While TypeScript provides a level of safety during development, TypeScript does not do runtime validation.
Popular runtime validation libraries include:
Typically you define a schema that describes the expected structure and format of the input data.
import { z } from "zod";export const subscribeSchema = z.object({name: z.string().max(50, "Name must be less than 50 characters").regex(/^[a-zA-Z\s'-]+$/,"Name can only contain letters, spaces, hyphens, and apostrophes",),email: z.email("Please enter a valid email address")});
At runtime, use the schema to validate the input data, providing detailed error messages if the input is invalid.
You should run server-side validation as well using the same library to ensure data integrity and protect against direct submissions to your API endpoints (e.g., by disabling JavaScript or manipulating requests).
While client-side validation is insufficient, it's still important for several reasons:
Validate user inputs on blur (i.e., after they move to the next input).
Early validation is often intrusive and annoying. If, however, you wait until form submission to show any errors, suddenly multiple errors can be displayed at once or your users may need to scroll to see the errors preventing form submission.
Validating user inputs on blur:
If a user fails to provide a valid email address, they should be prompted to correct it while still in context, not long after on form submission.
For each input, have a corresponding error field to display relevant errors. Consider wording, styling, and accessibility.
Provide clear wording
Follow these best practices for your messages.
Show distinct error styling
Your error messages should include visual cues to help users identify the error and related field.
Consider accessibility
For user-created errors (like form inputs), it's important to mark the invalid input with aria attributes to provide clear feedback to assistive technologies.
On error input, set aria-invalid="true"
using JavaScript.
<inputtype="email"aria-invalid="true"/>
Associate the error message using the aria-errormessage
attribute, linking it by referencing the id
.
<inputtype="email"aria-invalid="true"aria-errormessage="email-error"/><p id="email-error">Please enter a valid email address.</p>
You can also apply an aria-live
property or use a live region role like alert
for more direct feedback. A proper form library should include these accessibility details and is often the best way to ensure accessibility.
It's important to safeguard against bots without annoying users. Consider implementing one or more of these techniques:
Add a honeypot field
A honeypot field is a hidden input field that is ignored by real users, but often filled out by bots. You can add a honeypot field by using the hidden
attribute.
<input type="text" hidden name="honeypot" />
When the form is submitted, check to see if the form is filled out and reject the submission if it is.
Unfortunately, many bots cleverly avoid these honeypot fields, so we recommend only implementing this if you have also added at least one other bot detection method.
Add a CAPTCHA
A CAPTCHA is a test that is designed to be easy for humans to pass but difficult for bots to solve. Ideally, choose an invisible CAPTCHA that doesn't degrade the human experience while still providing a high level of security.
Consider one of the popular CAPTCHA services:
Beyond validation, sanitize and escape all user inputs before processing or rendering them. Sanitization helps prevent injection attacks such as SQL injection and cross-site scripting (XSS).
One popular library for sanitization is DOMPurify, although we recommend using Isomorphic DOMPurify if your backend is Node.js, as it provides the same functionality on both client and server.
import DOMPurify from 'isomorphic-dompurify';const sanitizedEmail = DOMPurify.sanitize(userEmail.trim());
On a related note, consider trimming inputs as shown above to ensure user inputs do not contain unnecessary whitespace.
If a user or bot avoids your current protections, it's important to monitor for suspicious patterns. These patterns can include:
You can protect against these types of attacks by implementing additional measures:
When observing suspicious patterns, be mindful that some methods involving user data (like storing IP addresses, etc.) may require disclosure to comply with privacy laws and regulations.
If receiving email addresses, consider verifying them upon form submission, which can help prevent high bounce rates and avoid damaging your sender reputation. View our Best Email Verification APIs Guide for more help.
Effective form validation should be useful and painless for good actors and frustrating and painful for bad actors. Regularly revisit your validation strategy, keep up with new best practices, and always put your users first.
By combining smart client-side checks, robust server-side validation, clear error messaging, and proactive bot/spam defenses, you’ll build forms that are both user-friendly and resilient against attacks.