When Netlify Forms is not working, the cause is almost always detection: Netlify only handles forms it finds in your site's static HTML at deploy time. If the form isn't detected, posts to it return a 404 or vanish. This guide walks through each common failure (a form 404, missing submissions, undetected React or Next.js forms, spam, AJAX and uploads) and the fix for each, based on Netlify's forms documentation.
Work through the sections in order. The first two fix most problems.
How Netlify Forms works (and why it breaks)
When you deploy, Netlify's build bots parse the HTML files in your publish directory. For each <form> with a netlify or data-netlify="true" attribute, Netlify:
- Registers a form using the form's
nameattribute. - Strips the
netlifyattribute. - Injects a hidden input:
<input type="hidden" name="form-name" value="contact" />.
At runtime, a POST that includes a form-name matching a registered form is captured. Anything Netlify didn't see at deploy time has nowhere to go. Keep that model in mind and most fixes follow from it.
1. Form detection is turned off
New sites need form detection switched on, and turning it on only takes effect from the next deploy.
How to check: in the Netlify UI open your site, then Forms. If you see an Enable form detection button, detection is off. On sites that used it before, look under Forms → Form detection.
Fix: enable form detection, then trigger a fresh deploy. Netlify's troubleshooting tips are explicit that deploys are only scanned for forms after you redeploy.
2. The form is missing the netlify attribute or a unique name
Netlify needs both an attribute and a name:
<form name="contact" method="POST" data-netlify="true">
<input type="text" name="name" required>
<input type="email" name="email" required>
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>Common mistakes:
- No
nameon the form. The form name is what appears in the dashboard, and every form on the site needs a different one. - Inputs without
nameattributes. Fields with nonameare never submitted, by Netlify or by any browser. method="GET"or no method. Usemethod="POST".
How to check: open your deployed page, view source (not DevTools, which shows the live DOM) and search for form-name. If the hidden input is there, Netlify detected the form. If it isn't, Netlify didn't.
3. Your form is rendered by JavaScript (React, Vue, Svelte)
This is the most common reason Netlify Forms isn't working in single-page apps. If React, Vue or another framework renders the form in the browser, the HTML file Netlify parses at deploy time has no <form> in it, so nothing gets registered.
Fix: add a hidden, static HTML version of the form to a real HTML file (for a Vite or Create React App project, index.html works), with the same name and the same field names:
<!-- Detected at deploy time, never shown to visitors -->
<form name="contact" data-netlify="true" netlify-honeypot="bot-field" hidden>
<input type="text" name="name">
<input type="email" name="email">
<textarea name="message"></textarea>
<input name="bot-field">
</form>Then add the form-name hidden input to the form your framework renders, because Netlify's build step can't inject it there:
<form name="contact" method="POST" onSubmit={handleSubmit}>
<input type="hidden" name="form-name" value="contact" />
{/* the same fields as the static form */}
</form>Every field you want to keep must exist in the static form. Netlify ignores fields it didn't see at deploy time.
4. Next.js on Netlify (and other SSR frameworks)
Server-rendered and pre-rendered pages aren't written out as the static HTML files Netlify scans, so Netlify attributes inside React components have no effect. For Next.js on the current OpenNext-based runtime (Next.js 13.5 and later), Netlify's Next.js forms guide asks you to:
- Create a static file in
public/, for examplepublic/__forms.html, that contains a hiddendata-netlify="true"form for each form, with every field name. - Submit with JavaScript (AJAX) by POSTing to that static file's path rather than relying on a full-page form submission.
await fetch("/__forms.html", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams(new FormData(event.target)).toString(),
});Watch for one more trap: the build fails if Netlify form attributes appear in your React code but there's no static form file in public/. Netlify documents a NETLIFY_NEXT_VERIFY_FORMS=false environment variable to skip that check, but adding the static file is the real fix. The same "static skeleton plus AJAX" pattern applies to Nuxt, SvelteKit and Remix apps that render forms on the server.
5. A 404 after submitting the form
A Netlify form 404 usually means one of two things.
The form wasn't detected. The POST goes to a URL Netlify has no handler for. Fix sections 1–4 first. If the form isn't listed under Forms in the dashboard, that's your problem.
The success page path is wrong. By default Netlify shows a generic success message. To send visitors to your own page, set action to a path:
<form name="contact" method="POST" data-netlify="true" action="/thank-you/">The path must be relative to the site root, start with /, and resolve to a page that actually exists in your deploy. On sites with clean URLs, watch the difference between /thank-you, /thank-you/ and /thank-you.html. Netlify's own tip is to add a normal link to the same path somewhere on the page and click it. If the link 404s, so will the form.
6. Submissions are missing (or in spam)
Netlify runs every submission through Akismet. Anything it flags goes to Spam submissions, not Verified submissions, and test messages are classic false positives.
How to check: open the form in the dashboard and switch to the spam list. You can mark a submission as verified from there.
To stop it happening during testing: use a real email address rather than test@test.com, write a real sentence in the message, and don't fire dozens of submissions from one IP in a row.
Submissions rejected by a honeypot or reCAPTCHA aren't kept at all. They don't appear in the spam list either. If you've added either, confirm the form's page shows Extra spam prevention as enabled, and that your honeypot field is genuinely hidden from humans:
<form name="contact" method="POST" data-netlify="true" netlify-honeypot="bot-field">
<p hidden><label>Don't fill this out: <input name="bot-field"></label></p>
<!-- other fields -->
</form>If browser autofill or a CSS mistake exposes bot-field, real visitors fill it in and Netlify quietly drops their messages.
Also check whether you renamed fields recently. The dashboard only shows fields from the latest deployed version of the form, so old data can look missing even though it's still available through Netlify's API.
7. AJAX submissions fail or arrive empty
When you submit with fetch (see submitting a form with JavaScript), three rules matter:
- URL-encode the body. Netlify Forms doesn't accept JSON. Send
application/x-www-form-urlencodedbuilt withURLSearchParams. - Include
form-name. If the JS form doesn't contain the hidden input, add it to the body yourself. - Post to a static path.
/works for most static sites. On Next.js, use your static forms file as shown above.
const data = new FormData(form);
data.set("form-name", "contact");
await fetch("/", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams(data).toString(),
});For forms with file uploads, send the FormData directly and don't set a Content-Type header. The browser has to add the multipart boundary itself. If you use reCAPTCHA, include the g-recaptcha-response field in the body.
8. File uploads fail
Netlify Forms supports <input type="file"> with a few documented limits:
| Limit | Netlify Forms |
|---|---|
| Files per field | One (use multiple fields for multiple files) |
| Maximum request size | 8 MB |
| Upload timeout | 30 seconds |
A multiple attribute on one input won't give you several files, and a large photo from a phone can push you past the request size. Check the current limits in Netlify's file upload docs.
Quick checklist
Detection on, site redeployed
Forms → form detection is enabled, and you've deployed since enabling it.
Form present in the deployed HTML
View source shows the form with a form-name hidden input, and the form appears in the Forms tab.
Unique name, named inputs, POST
Every field you need has a name and also exists in the static form.
JavaScript forms have a static twin
React, Vue and Next.js forms are mirrored in a static HTML file and include form-name.
Success page exists
The action path starts with / and loads when you visit it directly.
Spam list checked
You've looked in Spam submissions and tested with a realistic message.
If you'd rather not depend on build-time detection
Netlify Forms is a good fit when your site is plain static HTML that lives on Netlify. The friction shows up with JavaScript frameworks, server rendering and sites that might move hosts, because everything depends on what the build bots see at deploy time.
A hosted form backend takes the opposite approach: you get an endpoint URL, and the form posts to it at runtime. Nothing has to be detected, so React, Next.js and plain HTML forms all work the same way. FormSubmit is one option. Point the form's action (or your fetch call) at your endpoint and remove data-netlify:
<form action="https://formsubmit.app/f/YOUR_FORM_ID" method="POST">
<input type="text" name="name" required>
<input type="email" name="email" required>
<textarea name="message" required></textarea>
<input type="text" name="_gotcha" style="display:none" tabindex="-1" autocomplete="off">
<button type="submit">Send</button>
</form>JavaScript can send JSON, FormData or URL-encoded bodies. Spam filtering, email notifications and integrations like Slack, Google Sheets and webhooks are included on every plan, and the free plan covers one form and 50 submissions a month. The same form keeps working if you move to Vercel, Cloudflare Pages or GitHub Pages. See FormSubmit vs Netlify Forms for a fair comparison, or the Netlify static site guide for setup steps.