
How to Process HTML Forms
You've built the form. It looks fine in HTML, the labels are in place, and the button works in local dev. The annoying part is still open, though, because processing a form means deciding where the submission goes, how it's validated, what happens when it's spam, and how the data reaches the tools your team already uses.
The Three Paths You Can Take
The first question is rarely “how do I build the form?” It's “what should the browser post to?” A standard HTML form sends named fields to an endpoint through the <form> element, plus action and method, and if an input has no name, it doesn't make it into the payload at all, so the processing path starts with plain markup discipline (W3C form spec).

There are really three realistic paths. You can point the form at a self-hosted server you control, send it to a serverless function you write and deploy, or hand the plumbing to a hosted form backend that receives the submission and forwards it where it needs to go. If you want design inspiration before wiring anything up, Growform's best form examples is a useful gallery of patterns people ship.
Self-hosted server
This is the traditional route. You own the endpoint, the validation code, the persistence layer, and the response format, which is great when your workflow already lives in a full application.
Serverless function
This is the middle path for static sites that still need custom logic. The form posts to an API route or function, your code handles the request, and you decide whether to store, forward, or transform the payload.
Hosted form backend
This is the lowest-friction path for a static site. You keep the frontend simple, point action at a processing endpoint, and let the backend handle delivery, inboxing, spam checks, and integrations.
The right definition of “processing” includes more than a successful POST. It includes validation, delivery, storage, spam handling, and routing to email, spreadsheets, or other systems. That's the model to keep in mind while you choose a path, because the worst outcome is rebuilding the same form twice after the first integration choice turns out to be wrong.
The HTML Form Itself and Why Attributes Matter
A lot of form bugs are caused before any backend code runs. The browser can only send what the markup exposes, so the action, method, enctype, name, and label wiring all matter as much as the endpoint itself.
The attributes that control submission
Use POST for most real submissions, especially when you're dealing with private data, file uploads, or a server-side workflow that validates and stores the result. GET appends fields to the URL, which is fine for search forms, but it's not what you want for contact forms, lead forms, or anything sensitive.
File uploads need enctype="multipart/form-data", because plain form encoding won't carry files correctly. Every input that should reach the backend needs a name, and every field should have a <label> tied to a unique id so assistive tech can announce it properly. Massachusetts' web-form guidance is blunt about the basics, use descriptive labels, don't rely on placeholder text alone, and include type="submit" on the button for cross-browser support (Massachusetts web form guidance).
Practical rule: if a field doesn't have a
name, your backend can't read it.
Here's a clean reference form you can paste into a static site and wire to any of the three paths:
<form action="https://api.example.com/contact" method="POST" enctype="multipart/form-data">
<label for="name">Full name</label>
<input id="name" name="name" type="text" required>
<label for="email">Email address</label>
<input id="email" name="email" type="email" required>
<label for="message">Message</label>
<textarea id="message" name="message" rows="6" required></textarea>
<input type="hidden" name="subject" value="Website contact form">
<input type="hidden" name="redirectTo" value="https://example.com/thank-you">
<button type="submit">Send message</button>
</form>The hidden fields are useful when the backend supports them, because they let you control subject lines, redirect targets, or internal routing without asking the user for extra input. If you want a broader reference for field choices, the internal guide on form input types is a good companion to this markup.
React, Next.js, and Vue examples
The same structure works in frameworks. In React, don't turn the form into a fancy client-side state machine unless you need to, just submit the markup and let the backend do the processing.
export default function ContactForm() {
return (
<form action="https://api.example.com/contact" method="POST">
<label htmlFor="email">Email address</label>
<input id="email" name="email" type="email" required />
<label htmlFor="message">Message</label>
<textarea id="message" name="message" required />
<button type="submit">Send</button>
</form>
);
}In Next.js or Vue, the same rule applies, keep the names consistent with what the backend expects. If the backend expects email and message, don't rename them to userEmail and body in the frontend unless you're deliberately mapping fields on the server side.
Processing Forms With a Serverless Function
Serverless is the choice when you want your own logic without running your own server. The form posts to a function, the function validates the payload, and then it can send mail, call a webhook, or return field-level errors to the browser.
A minimal Next.js API route
This example expects POST data, checks a small whitelist of fields, and returns JSON that your frontend can read. Server-side validation matters even if the frontend already validates, because client checks are easy to bypass.
// pages/api/contact.js
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ ok: false, error: 'Method not allowed' });
}
const { name, email, message } = req.body || {};
const errors = {};
if (!name || name.trim().length < 2) errors.name = 'Please enter your name.';
if (!email || !email.includes('@')) errors.email = 'Please enter a valid email address.';
if (!message || message.trim().length < 10) errors.message = 'Please enter a longer message.';
if (Object.keys(errors).length) {
return res.status(400).json({ ok: false, errors });
}
// Send email, call webhook, or store the submission here.
return res.status(200).json({ ok: true });
}The shape of the response matters. A browser form can redirect, but a single-page app usually wants JSON so it can show a success state or field errors inline. The same idea works in a Netlify Function or Vercel Function, because the core issue isn't the platform, it's whether your handler can read the payload and reject bad input cleanly.
What this path gives you and what it costs
You get full control over validation, retry logic, and what happens after receipt. You also own the annoying parts, like spam filtering, file storage, GDPR requests, webhook failures, and uptime.
If you're already maintaining a backend for other reasons, a serverless form handler is usually a sensible extension of that work. If the form is the only backend thing on the site, it can become a lot of operational overhead for a very small feature.
For teams shipping static sites, that trade-off often decides the path. A serverless function is a good fit when you need custom branching logic, but it's not a shortcut around backend responsibilities.
Using a Hosted Form Backend Like Static Forms
A hosted backend is the fastest way to get from HTML form to working submission without writing your own processing code. The pattern is simple, the form posts to the service endpoint, the backend handles delivery and storage, and the same markup can work in plain HTML, React, Next.js, or Vue.

Plain HTML
A typical hosted form setup points action at a processing endpoint and includes a service-specific API key as a hidden field.
<form action="https://api.staticforms.dev/submit" method="POST" enctype="multipart/form-data">
<input type="hidden" name="accessKey" value="YOUR_ACCESS_KEY">
<input type="hidden" name="subject" value="New contact form submission">
<input type="hidden" name="redirectTo" value="https://example.com/thank-you">
<input type="hidden" name="honeypot" value="">
<label for="name">Full name</label>
<input id="name" name="name" type="text" required>
<label for="email">Email address</label>
<input id="email" name="replyTo" type="email" required>
<label for="message">Message</label>
<textarea id="message" name="message" required></textarea>
<label for="file">Attachment</label>
<input id="file" name="attachment" type="file">
<button type="submit">Send</button>
</form>For single-page apps, the same form can request a JSON response instead of a redirect by sending an Accept: application/json header. That keeps the user on the page and lets you show inline success or error UI instead of bouncing them to a thank-you page.
React, Next.js, and Vue
export function ContactForm() {
return (
<form action="https://api.staticforms.dev/submit" method="POST">
<input type="hidden" name="accessKey" value="YOUR_ACCESS_KEY" />
<input type="hidden" name="redirectTo" value="https://example.com/thanks" />
<label htmlFor="email">Email</label>
<input id="email" name="replyTo" type="email" required />
<label htmlFor="message">Message</label>
<textarea id="message" name="message" required />
<button type="submit">Send</button>
</form>
);
}<template>
<form action="https://api.staticforms.dev/submit" method="POST">
<input type="hidden" name="accessKey" value="YOUR_ACCESS_KEY" />
<label for="name">Name</label>
<input id="name" name="name" type="text" required />
<label for="message">Message</label>
<textarea id="message" name="message" required></textarea>
<button type="submit">Send</button>
</form>
</template>What to verify after you wire it up
- Send a valid test submission. Confirm that the inbox, redirect, or webhook receives the data you expected.
- Send an invalid one. Make sure the backend rejects bad input instead of trying to “fix” it.
- Test spam controls in production. A honeypot or CAPTCHA behaves differently once the live form is public.
Static Forms is one hosted option here. It identifies forms by API key, processes submissions without a custom backend, stores them in a dashboard inbox with CSV export, and can forward data to email or integrations. For teams building static sites, that's often the simplest way to get a form into production without also maintaining a dedicated handler.
Comparing the Three Approaches Honestly
The right choice depends on what you're optimizing for. A five-page portfolio site, a client brochure site, and a SaaS lead form don't deserve the same architecture.

The basic form architecture hasn't changed in decades, which is why these choices still look familiar. The <form> element, action, method, and optional enctype still define the contract between browser and backend, whether the backend is a CGI script, a serverless function, or a hosted service (Berkeley CGI notes).
Decision criteria that matter
| Criterion | Serverless Function | Hosted Backend (Static Forms) |
|---|---|---|
| Setup Complexity | Medium, because you write and deploy code | Low, because the submission endpoint already exists |
| Maintenance | Medium to high, depending on validation, retries, and storage | Low, because the service handles the plumbing |
| Customization | Full, if you're willing to code it | Limited to the backend's supported fields and integrations |
| Cost | Pay-per-use or platform-based | Free tier for small sites, then paid tiers for more usage |
| Best fit | Teams that need custom logic | Static sites that want a simple submission flow |
A freelancer building a brochure site usually wants the shortest path to a working submission. A startup running lead gen may care more about field routing, webhook control, and internal data flow. Neither answer is universally better.
If you want a second opinion on validating the submit flow from a product perspective, Uxia's landing page usability testing insights are worth reading before you settle on one path, because the form itself is only part of the experience.
Spam Protection, File Uploads, and GDPR
Once the form submits correctly, the next problems are the ones that tend to show up in production. Spam lands in the inbox, users attach the wrong file type, and privacy questions start when the submission contains personal data.
Spam controls that actually get used
There are four common approaches, and they don't solve the same problem. reCAPTCHA v2 adds an explicit challenge, reCAPTCHA v3 scores the request invisibly, Cloudflare Turnstile is often chosen as a lighter user experience, and a honeypot field catches low-effort bots without making a human do anything.
A honeypot is still worth adding even if you use CAPTCHA, because it's cheap and silent. It won't stop a targeted attacker, but it does block a lot of automated junk before it reaches your inbox or webhook.
The form-processing guidance from OWASP treats input validation, output encoding, query parameterization, and transaction-token verification as critical controls, and it also recommends HTTPS, secure cookies, and session checks for protected submissions (OWASP form-processing guidance). That's the right mindset for hosted forms too, because the browser is just one hop in a larger workflow.
File uploads and payload checks
File uploads need multipart/form-data, otherwise the browser won't send the file correctly. Hosted form backends commonly cap uploads at 5MB per submission, so it's smart to validate size and file type before the user waits for a rejected upload, especially on mobile connections.
Practical rule: validate file type and file size before submission, then validate again on the backend after receipt.
That double check matters because front-end constraints are easy to bypass. If your form accepts resumes, images, or screenshots, make the accepted file types explicit in the UI and reject anything unexpected on the server side.
GDPR and mail deliverability
For GDPR-sensitive forms, show clear consent text, explain what the submission is used for, and be ready to handle export or deletion requests. If you're using a hosted backend, know where the data is stored and who can access the inbox, because the legal and operational responsibility doesn't disappear just because you didn't write the code.
Custom-domain email delivery needs proper authentication too. SPF, DKIM, and DMARC all help keep submission emails from landing in spam or being rejected by mailbox providers. If form notifications are important to your team, email authentication is part of form processing, not a separate ops task.
If you want a deeper checklist for abuse controls, the internal write-up on form spam protection covers the practical side of combining honeypots, CAPTCHA, and backend checks.
Routing Submissions to the Tools You Already Use
A processed form is more useful when it lands where people already work. A contact request that only sits in a backend inbox is easy to miss, while the same submission can be emailed, logged, and forwarded to collaboration tools at the same time.

Common destinations
A hosted backend can turn one submission into several downstream actions.
- Google Sheets: append each submission as a new row for lightweight tracking.
- Slack or Discord: post a formatted message to a channel so the team sees it quickly.
- Notion or Airtable: create a record with field mapping for more structured follow-up.
- Mailchimp: add the submitter to an audience when the form is for newsletter signup.
- Webhook: send a JSON POST to any URL, then let Zapier, Make, or n8n fan it out further.
That webhook path is the most flexible route because it decouples the form from the rest of your stack. If you need to understand that pattern in more detail, the internal primer on what a webhook is and how it works is a useful companion.
Why routing matters in practice
The best backend is the one that reduces manual copying. If sales wants leads in Google Sheets, support wants alerts in Slack, and marketing wants newsletter signups in Mailchimp, the form backend should do that work automatically.
Static Forms can route one submission to multiple destinations at once, so a single contact form can notify you, log the lead, and pass the address to your audience list without extra glue code. That kind of routing is often what turns a form from a passive input field into a usable workflow.
A CTA for Static Forms. If you want to ship a static-site form without building and maintaining your own handler, point the form at a hosted backend, test a real submission, and wire the destinations you use before you launch.
Related Articles
10 Best Free Form Processing Services for 2026
Compare the top 10 free form processing service options for static sites. Find the best backend with detailed pros, cons, limits, and code examples.
Create WordPress Form Without Plugin: Native PHP & External
Learn to build a custom wordpress form without plugin. This guide explores native PHP methods & modern external APIs, with practical code examples for 2026.
Mastering Form Validation JavaScript: A 2026 Guide
Learn robust form validation javascript. This guide covers HTML5, custom validation, regex, accessibility, and backend integration for secure forms.