← All articles
Static SitesForm BackendJamstack

How to add a form to a static site without a backend

FormsFort Team ·

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:

  1. Build your form with standard HTML
  2. Set the action attribute to the form backend URL
  3. Include a public access key to identify your form
  4. 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.

Astro

Astro generates static HTML by default. Add a form directly in any .astro file:

src/pages/contact.astro
---
---
<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:

DestinationUse case
EmailContact forms, support requests
WebhookTrigger automations in Zapier, Make, or custom systems
Google SheetsCollect responses in a spreadsheet
Slack / Discord / TelegramTeam notifications for new submissions
AutoresponderSend 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.