# Diagnose Netlify Forms: not detected, submissions missing, or 404 on submit

Use this when a Netlify-handled form doesn't work. There are three distinct
failure modes with different fixes, so classify first: **(A)** the form was
never registered (detection), **(B)** the form is registered but submissions
don't appear, **(C)** submissions return 404.

## 1. Is the form registered at all?

Form detection runs during post-processing of each deploy: Netlify scans the
*built, published HTML* for `&lt;form&gt;` elements carrying `data-netlify="true"`
or the bare `netlify` attribute, and registers each one under its `name`
attribute. Check the site's Forms tab in the Netlify UI for your form's name.

- **Name absent → failure mode A (detection).** Go to section 2.
- **Name present → failure mode B or C.** Go to section 3.

## 2. Mode A: the form was never detected

Causes, in likelihood order:

1. **The form isn't in the built HTML.** Detection only sees the static HTML
   produced at build time. If the form is rendered client-side (React, Vue,
   Next.js, Astro, SvelteKit), the parser never sees it. Fix: add a static
   skeleton file in the publish directory (conventionally
   `public/__forms.html`) containing a hidden copy of every form, with exactly
   the same `name`, field names, and honeypot field:

```html
&lt;form name="contact" data-netlify="true" netlify-honeypot="bot-field" hidden&gt;
  &lt;input type="hidden" name="form-name" value="contact" /&gt;
  &lt;input type="text" name="name" /&gt;
  &lt;input type="email" name="email" /&gt;
  &lt;textarea name="message"&gt;&lt;/textarea&gt;
  &lt;input name="bot-field" /&gt;
&lt;/form&gt;
```

The `hidden` attribute keeps it invisible; the file must ship in the publish
directory. One skeleton file can hold all of your forms.

2. **Missing or mismatched attributes.** The form needs `data-netlify="true"`
   (or bare `netlify`) *and* a `name` attribute. On AJAX posts, the request
   body must also carry a hidden input whose `name` is `form-name` and whose
   `value` exactly equals the form's `name`:

```html
&lt;form name="contact" method="POST" data-netlify="true"&gt;
  &lt;input type="hidden" name="form-name" value="contact" /&gt;
  <!-- ... fields ... -->
&lt;/form&gt;
```

3. **Detection was off, or the fix hasn't been deployed.** Form detection must
   be enabled in site settings, and every markup fix needs a new deploy —
   detection only runs on the latest deploy's HTML.

After redeploying, confirm the form name appears in the Forms tab *before*
testing again. Submissions made before registration are lost.

## 3. Mode B: the form is registered, but submissions are missing

1. **Check the spam tab first.** Netlify filters submissions through Akismet.
   Legitimate-looking test submissions get flagged constantly: sending from a
   throwaway address, one-line messages, or rapid-fire tests from one IP are
   classic spam signals. Test with a real email address and a realistic
   message, then look in the Spam list and mark it verified.
2. **Check the `form-name` hidden input on AJAX submissions.** Without
   `&lt;input type="hidden" name="form-name" value="[exact form name]"&gt;` in the
   POST body, Netlify can't route the submission and it silently fails.
3. **Check field names against the registered form.** Netlify validates
   submitted field names against the registered form definition — a renamed
   input in your component won't appear where you expect. (The UI shows only
   the fields from the last deployed form version; old submissions keep their
   data but won't show the new fields. Mark fields hidden instead of deleting
   them if you need continuity.)
4. **Don't hand-roll reCAPTCHA next to Netlify's.** If you're doing custom
   reCAPTCHA verification inside your own function and POSTing to the form
   endpoint yourself, Netlify's form processing may never run. Use Netlify's
   own reCAPTCHA integration (`data-netlify-recaptcha="true"` plus the
   recaptcha div) instead of a parallel custom flow.

## 4. Mode C: 404 on submit

POSTing to a form name that isn't registered returns 404 — and so does
posting after the form was deleted (deletion is permanent; export a CSV
first). Fix: go back to section 2, register the form, redeploy, and confirm
it before resubmitting. For AJAX, POST to `/` (or the page's own path) with
`Content-Type: application/x-www-form-urlencoded`.

## 5. Checklist

1. Forms tab: is the name listed? No → detection problem (built HTML, skeleton
   file, attributes, redeploy).
2. Listed but nothing arrives → spam tab (Akismet), then the `form-name`
   hidden input, then field-name match.
3. 404 → the form was deleted or never registered; register and redeploy.
4. Every markup fix requires a new deploy — detection only sees the latest
   build's HTML.
5. Test with a real email and realistic content, not throwaway one-liners.
