HTML Contact Form Without a Backend
Add a contact form to any HTML page and get each message by email
Last updated
To add a contact form to an HTML page without a backend, set the form's action to https://api.staticforms.dev/submit and add your form's API key as a hidden apiKey field. Static Forms receives the POST and emails each submission to you, so there is no server code to write and no JavaScript to add. Copy the form below, replace YOUR_API_KEY, and upload the page to any host. It works on the free plan, with 250 submissions every 30 days.
1. Copy the form
Find the key on the General tab of your form in the app. Every field you add with a name appears in the notification email, so add phone, company or a select the same way.
| Field | What it does |
|---|---|
apiKey | Required. Tells Static Forms which form the submission belongs to. |
email | Becomes the reply-to address on the notification, so you can answer by replying. |
replyTo | Optional. A reply-to address that wins over the email field. |
subject | Optional. The subject line of the notification email, unless the form has its own subject in an email template or rule. |
redirectTo | Optional. A full http:// or https:// URL to send the visitor to after a successful submission. |
honeypot | Any field whose name contains "honeypot". Leave it hidden; a submission that fills it in is treated as spam. |
2. Show a thank-you page
With a redirectTo field, a successful submission answers with a 303 redirect to that URL. It must be an absolute http:// or https:// URL; a relative path such as /thanks is ignored. Without redirectTo, the browser shows a plain "Form submitted successfully" page, so most sites add their own thank-you page. You can also set the page without touching your HTML, under Redirect after submit on the form's General tab; a redirectTo field in the page wins over it. To keep the visitor on the same page instead, use the JavaScript version below.
3. Keep spam out
The built-in spam filter is on by default on every plan, and the honeypot field in the form above catches bots that fill in every field. For more, add a CAPTCHA: turn it on in your form's Security settings, then add its widget to the form. Once a CAPTCHA is on, a submission without a valid token is rejected with a 403.
| Protection | Plan | Field it sends |
|---|---|---|
| Built-in spam filter | All plans | Nothing to add |
| Honeypot field | All plans | honeypot |
| reCAPTCHA v2 | Free and up | g-recaptcha-response |
| Cloudflare Turnstile | Starter and up | cf-turnstile-response |
| ALTCHA | Starter and up | altcha |
| hCaptcha | Starter and up | h-captcha-response |
| reCAPTCHA v3 | Starter and up | g-recaptcha-response |
reCAPTCHA v2 (Free plan)
Create a reCAPTCHA v2 "I'm not a robot" key pair in the Google reCAPTCHA admin console, enter the site key and secret key in your form's Security settings, and add the widget. Google's script puts the token in the form for you.
Cloudflare Turnstile (Starter plan and up)
Create a Turnstile widget in the Cloudflare dashboard, enter its site key and secret key in your form's Security settings, and add the widget. Turnstile adds a hidden cf-turnstile-response field to the form.
ALTCHA (Starter plan and up)
ALTCHA needs no third-party account: turn it on in your form's Security settings and Static Forms creates its keys. The widget fetches a challenge from Static Forms with your API key, solves it in the browser and sends the answer as the altcha field.
4. Accept file uploads (Starter plan and up)
Add enctype="multipart/form-data" to the form and a file input. Files are attached to the notification email when one is sent and stored with the submission, so you can download them from the Inbox later. The whole request, files and fields together, must stay under 4.5 MB; a larger one is refused with a 413. On the Free plan a submission that includes a file is rejected with a 400 that says file uploads need the Starter plan. See File Uploads for more.
5. Optional: submit without leaving the page
This version posts the same form with fetch and shows the result on the page. Leave out redirectTo here: a redirect would send fetch to your thank-you page instead of returning the JSON result. A URL saved under Redirect after submit doesn't affect this version. The response is { "success": true, "message": "Form submitted successfully" }, or { "success": false, "error": "..." } with the reason.
6. Test it
Open the page from your host, or from localhost while you build, and send a message. It arrives by email and is also listed in the Inbox in the app. The form works on any static host, including GitHub Pages, Netlify, Cloudflare Pages and Vercel.
Troubleshooting
| What you see | Why, and what to do |
|---|---|
| A plain "Form submitted successfully" page | The submission worked. Add a redirectTo field, or set Redirect after submit on the form's General tab, to show your own thank-you page. |
| redirectTo is ignored | It must be a full URL that starts with http:// or https://, not a relative path. |
| "Invalid API key" | Check that the apiKey value matches the key on the form's General tab, with no extra spaces. |
| 403 "CAPTCHA verification failed" (reCAPTCHA), or "Turnstile", "hCaptcha" or "Altcha verification failed" | A CAPTCHA is turned on for the form, but the submission had no valid token. Add the widget to the form, or turn the CAPTCHA off. |
| 403 "Unable to determine origin domain" | Domain restriction is on, and the request had no Origin or Referer header. Submit from a page on an allowed domain. |
| 400 "File uploads require the Starter plan or above" | Remove the file input, or upgrade the plan. |
| 413 | The request is over 4.5 MB. Ask for smaller files. |
| 429 "Rate limit exceeded" | Too many submissions in a short time. On Free, the limits are 10 a minute per account and 30 an hour per API key. |
| No email arrives | Check your spam folder, then the Inbox in the app: if the submission is listed, the problem is delivery. |
More fixes are in Common Issues and Email Delivery.
Build the form visually
Prefer not to write markup? The Form Builder designs a form in the app and embeds it with one script tag.
FAQ
Does this work on the free plan?
Yes. The snippet only uses Free plan features: 250 submissions every 30 days, 1 form endpoint, email delivery, the built-in spam filter, a honeypot field and reCAPTCHA v2. No credit card is needed.
Do I need JavaScript or a server?
No. The form is plain HTML that posts straight to https://api.staticforms.dev/submit. Static Forms receives the submission and emails it to you. JavaScript is only needed if you want to show the result without leaving the page.
How do I keep spam out?
Every submission is checked by the built-in spam filter on every plan. The snippet adds a hidden honeypot field (any field whose name contains "honeypot"); bots fill it in and those submissions are dropped. reCAPTCHA v2 is available on Free. Cloudflare Turnstile, hCaptcha, ALTCHA and reCAPTCHA v3 are available from the Starter plan.
Is it safe to put the API key in my HTML?
Yes. The form API key is meant to be public. It cannot read your submissions or sign in to your dashboard. From the Starter plan you can also restrict submissions to your own domains.
What does the visitor see after sending?
The page you set in redirectTo, through a 303 redirect. Without redirectTo, a plain "Form submitted successfully" page. With the fetch version, whatever message your script shows.
Can visitors attach files?
Yes, from the Starter plan. Add enctype="multipart/form-data" and a file input; the whole request must stay under 4.5 MB.









