How to add a form to a static site without a backend
Static site generators like Astro, Hugo, Next.js, 11ty, Jekyll, and Gatsby produce fast, secure websites by pre-rendering HTML at build time. The tradeoff is that there is no server to process form submissions.
This guide explains how to add fully functional forms to any static site without writing backend code, serverless functions, or API routes.
Why static sites cannot process forms
A static site serves pre-built HTML files from a CDN or file host. When a visitor submits a form, the browser sends a POST request to the URL in the form’s action attribute. On a traditional server-rendered site, that URL points to a PHP script, Node.js route, or serverless function that processes the data.
On a static site, there is no server to receive that POST request. You need an external service to handle it.
The form backend pattern
A form backend service provides a public API endpoint that receives form submissions. The pattern is simple:
- Build your form with standard HTML
- Set the
actionattribute to the form backend URL - Include a public access key to identify your form
- The service receives the submission, runs spam checks, and delivers the data
<form action="https://api.formsfort.com/submit" method="POST"> <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" /> <label for="email">Email</label> <input type="email" id="email" name="email" required /> <label for="message">Message</label> <textarea id="message" name="message" required></textarea> <button type="submit">Send</button></form>This works on every static site generator because it is plain HTML. No build plugins, no framework-specific code, no serverless functions.
Adding forms to popular static site generators
Astro
Astro generates static HTML by default. Add a form directly in any .astro file:
------
<form action="https://api.formsfort.com/submit" method="POST"> <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" /> <label for="name">Name</label> <input type="text" id="name" name="name" required /> <label for="email">Email</label> <input type="email" id="email" name="email" required /> <label for="message">Message</label> <textarea id="message" name="message" required></textarea> <button type="submit">Send message</button></form>Next.js static export
When using output: "export" in Next.js, your site produces plain HTML files. Add forms using standard HTML in any page component:
export default function ContactPage() { return ( <form action="https://api.formsfort.com/submit" method="POST"> <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" /> <label htmlFor="email">Email</label> <input type="email" id="email" name="email" required /> <label htmlFor="message">Message</label> <textarea id="message" name="message" required /> <button type="submit">Send</button> </form> );}Hugo
Hugo templates support standard HTML. Add a form in any layout or content file:
<form action="https://api.formsfort.com/submit" method="POST"> <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" /> <label for="name">Name</label> <input type="text" id="name" name="name" required /> <label for="email">Email</label> <input type="email" id="email" name="email" required /> <button type="submit">Send</button></form>11ty (Eleventy)
Eleventy outputs static HTML. Add forms in Nunjucks, Liquid, or Markdown files:
<form action="https://api.formsfort.com/submit" method="POST"> <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" /> <label for="email">Email</label> <input type="email" id="email" name="email" required /> <button type="submit">Subscribe</button></form>Jekyll
Jekyll sites hosted on GitHub Pages or any static host can use form backends:
<form action="https://api.formsfort.com/submit" method="POST"> <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" /> <label for="name">Name</label> <input type="text" id="name" name="name" required /> <label for="email">Email</label> <input type="email" id="email" name="email" required /> <label for="message">Message</label> <textarea id="message" name="message" required></textarea> <button type="submit">Send</button></form>Protecting static site forms from spam
Static site forms are visible to bots that crawl HTML source. Protect them with multiple layers:
Honeypot field
Add a hidden field that bots fill in but humans do not see:
<input type="text" name="botcheck" style="position: absolute; left: -9999px; opacity: 0;" tabindex="-1" autocomplete="off"/>Domain restriction
Configure your form backend to only accept submissions from your domain. This prevents other sites from using your form endpoint.
Rate limiting
Form backends enforce rate limits per IP address, preventing a single source from flooding your form with submissions.
CAPTCHA
For high-traffic forms, add a CAPTCHA challenge. Cloudflare Turnstile is a privacy-friendly option that does not require image puzzles:
<div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div><script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>Where to deliver submissions
Form backend services support multiple delivery destinations:
| Destination | Use case |
|---|---|
| Contact forms, support requests | |
| Webhook | Trigger automations in Zapier, Make, or custom systems |
| Google Sheets | Collect responses in a spreadsheet |
| Slack / Discord / Telegram | Team notifications for new submissions |
| Autoresponder | Send a confirmation email to the submitter |
Handling success and error states
Redirect after submission
Add a hidden _redirect field to send users to a thank-you page:
<input type="hidden" name="_redirect" value="https://example.com/thanks" />AJAX submission
For a smoother experience without page reloads, use JavaScript:
document.querySelector("form").addEventListener("submit", async (e) => { e.preventDefault(); const form = e.target; const response = await fetch(form.action, { method: "POST", body: new FormData(form), }); if (response.ok) { document.getElementById("status").textContent = "Sent successfully."; form.reset(); }});Summary
Static sites handle forms by delegating submission processing to a hosted form backend. The pattern is universal across all static site generators: write standard HTML, point the action at a form backend URL, and include an access key. Add spam protection with honeypot fields, domain restrictions, and CAPTCHA. Configure delivery to email, webhooks, Google Sheets, or messaging platforms.
No server-side code, no serverless functions, no build plugins required.
Frequently asked questions
How do static sites handle form submissions?
Static sites cannot process form submissions on their own because they serve pre-built HTML files. To handle forms, you point the form action attribute at a hosted form backend service that receives, validates, and delivers the submission data.
What is a form backend service?
A form backend service is a hosted API endpoint that receives HTML form submissions, runs spam checks, and forwards the data via email, webhook, or spreadsheet. Examples include FormsFort, Formspree, and Getform.
Can I use forms on a Next.js static export?
Yes. Next.js static exports generate plain HTML files with no server. Add a standard HTML form pointing at a form backend service, just like you would on any static site.
Do I need serverless functions for static site forms?
No. A hosted form backend service replaces the need for serverless functions, API routes, or any server-side code. You only need HTML markup.
Is it secure to expose a form endpoint in static HTML?
Yes, when using a form backend with built-in spam protection. The endpoint uses a public access key (not a secret), and the service enforces domain restrictions, rate limits, honeypot checks, and CAPTCHA verification.
Get started free
Ready to add forms to your static site?
No backend required. Point your HTML form at FormsFort and start receiving submissions in minutes.