Why a static site needs a form backend
A static site is files: HTML, CSS, images and maybe some JavaScript, served as they are by a host such as Netlify,
Vercel, Cloudflare Pages or GitHub Pages. That is why static sites are fast, cheap and hard to break. It is also
why a contact form on one does nothing on its own. When a visitor presses Send, the browser posts the form to
the address in its action, and on a static site there is no code at that address to receive it,
store it or tell you about it.
A form backend is that missing piece: a service whose only job is to receive what forms send, keep it, and pass it on to wherever you work, usually email, Slack or a webhook. Your form, its markup and its design stay yours.
The five ways to do it
1. Your host’s form feature
Some hosts receive forms for you. Netlify Forms is the best known: add an attribute to the form and Netlify collects what it sends. Since April 2026, Netlify’s forms are free and unlimited on its credit-based plans[2]. It is the least setup when you are already on that host, and it ties your forms to it: move hosts and the forms stop working.
2. A hosted form backend
A service such as Formspree, Forminit (formerly Getform), Basin, Web3Forms or OllieLeads gives each form an endpoint URL. You set the form’s action to it, and that is the integration. They differ in free limits, spam filtering, where leads are delivered, and extras such as file uploads, webhooks or catching people who leave a form halfway.
3. A serverless function you write
Every major static host also runs small server functions: Netlify Functions, Vercel Functions, Cloudflare Pages Functions and Workers. You write the handler that receives the post, then send an email through a provider or save to a database. Cloudflare’s free tier covers 100,000 requests a day[7]; Vercel’s free Hobby plan is for personal, non-commercial use[8]. You get full control and no extra vendor, and you also own spam, retries, storage and what happens when the email provider is down.
4. An embedded form from another tool
Google Forms, Typeform and Tally can be embedded in an iframe. They handle everything, and the form is no longer yours: it looks like their product, sits outside your page’s design and analytics, and is hard to change.
5. A mailto: link
A mailto: link opens the visitor’s mail app with your address filled in. It needs nothing at all,
and it fails for anyone without a configured mail app, which on a phone or a work laptop is many people. Treat
it as a fallback link next to a real form, never as the form.
Hosted form backends compared
Free tiers change often, so this table is dated: figures are from each vendor’s own public pricing page, checked on 17 October 2026[1–6]. We make OllieLeads, so read its row with that in mind, and check each vendor’s page before you decide.
| Service | Free tier | Paid from | Worth knowing |
|---|---|---|---|
| Netlify Forms | Free and unlimited on Netlify’s credit-based plans (since April 2026) | Personal plan from $9 a month | Only on sites hosted by Netlify; accounts created before September 2025 may have older per-site limits |
| Formspree | 50 submissions a month, unlimited forms, 30-day archive | Personal: $15 a month, or $10 a month yearly, for 200 a month | Spam filtering included; file uploads on paid plans |
| Forminit (formerly Getform) | 100 submissions a month, 1 form | Pro: $16 a month billed yearly, for 5,000 a month | Getform was renamed Forminit in January 2026 |
| Basin | 50 submissions a month, 1 form, 30-day retention | Starter: $12.50 a month billed yearly, for 250 a month | 14-day trial on paid plans |
| OllieLeads | 500 leads in total, no credit card | Pro: $29 a month for 1,000 leads a month | Catches people who leave halfway; Slack, Discord, webhooks and remote MCP; paid capture never stops (that’s us) |
How to choose
Five questions settle it for most sites:
- How many leads a month? Compare against free limits, and check whether a limit is per month or in total, and whether spam counts toward it.
- What happens when you pass the limit? Some services stop accepting the form until the next month or until you pay. A form that silently stops is the most expensive kind.
- Where do you want leads? Email only, or Slack, Discord, a CRM, a webhook or an AI agent too.
- Long or multi-step forms? Those lose the most people halfway; see our form abandonment guide for what to do about it.
- Will you move hosts? If so, avoid a form feature that only works on one host.
Set one up in five minutes
Whichever service you pick, the setup has the same shape.
Point the form at the endpoint
Create a form in the service, copy its endpoint URL, and put it in the form’s action with method="post". Give every field a clear name: the name is how each answer is labeled
when it reaches you. Add autocomplete attributes, which make the form faster to fill on phones.
<form action="https://formbackend.example/f/your-form-id" method="post"> <label>Name <input name="name" autocomplete="name" required></label> <label>Email <input name="email" type="email" autocomplete="email" required></label> <label>Message <textarea name="message"></textarea></label> <!-- Where to send people afterwards (most backends support this) --> <input type="hidden" name="_next" value="https://example.com/thanks"> <button>Send</button> </form>
Or send it in the background
To keep the visitor on the page, send the form with JavaScript and show your own message. Give the request a
timeout and always tell the visitor what happened. Note the trap: building the body with Object.fromEntries(new FormData(form)) is common, and it keeps only the last of several ticked
checkboxes.
form.addEventListener('submit', async (e) => { e.preventDefault(); const data = {}; for (const [k, v] of new FormData(form)) { // Keep every ticked checkbox: a plain object would keep only the last data[k] = k in data ? [].concat(data[k], v) : v; } const res = await fetch(form.action, { method: 'POST', headers: { 'content-type': 'application/json', accept: 'application/json' }, body: JSON.stringify(data), signal: AbortSignal.timeout(5000) }).catch(() => null); showMessage(res?.ok ? 'Thanks, we got it.' : 'Something went wrong. Email us instead.'); });
Test it, then test it again on a phone
Send one test, check it arrived where you expected, then send one from your phone on mobile data. Most services have a test mode or a way to mark tests; use it so tests don’t count toward your limit.
Keep spam out
Any public form gets spam within days. Three layers stop almost all of it without bothering people:
- A honeypot field: a field hidden from people that bots fill in. Your form backend drops any post where it is filled.
- Allowed sites: many backends accept posts only from the domains you list, so a bot cannot post from elsewhere in a browser.
- A challenge such as Cloudflare Turnstile, for forms that are attacked anyway. Prefer invisible challenges; every puzzle costs you real people.
<!-- Hidden from people; bots fill it in, and the post is dropped --> <div aria-hidden="true" style="position:absolute;left:-9999px"> <label>Leave this empty <input name="_hp" tabindex="-1" autocomplete="off"></label> </div>
Each service names its honeypot differently, for example _gotcha or _hp. Use the name from your service’s docs, or the field does nothing.
Mistakes to avoid
- A secret key in the page. Anything in HTML or client JavaScript is public. A form endpoint is fine; an API key that can read leads is not.
- No message after Send. If the visitor can’t tell it worked, they send it three times or not at all.
- Losing the visitor’s typing on an error. Validate in the browser, and never clear the form when something fails.
- Nobody watching the inbox. Send leads where you actually look, and test the whole path, not just the form.
- A limit that stops the form. Know what your service does at the limit before a busy month finds out for you.
Questions people ask
What is a form backend?
A form backend is a service that receives what a web form sends, stores it and passes it on, usually by email, chat or webhook, so a site without its own server can still collect leads. The site keeps its own form; the form backend is the address the form posts to.
Can a static site have a contact form without a backend?
A static site cannot store or email form data by itself, because it has no server code. It needs somewhere to send the data: a hosted form backend, a form feature of the hosting platform, or a small serverless function you write. A mailto: link is the only option with no backend, and it loses most people.
Is it safe to put a form endpoint URL in public HTML?
A form endpoint URL is meant to be public: it can only receive data, never read it back. What must never appear in public HTML or JavaScript is a secret key or an API key that can read or delete data. Pair the endpoint with spam protection and, where offered, a list of allowed sites.
Do I need JavaScript to send a form to a form backend?
A form backend works with a plain HTML form and no JavaScript at all: set the form action to the endpoint and method to post. JavaScript is only needed to send in the background, show a message without leaving the page, or save details before the visitor submits.