Troubleshooting

400 Bad Request: How to Fix a Failed Form Submission

A 400 Bad Request on your SimpleForm endpoint almost always traces back to your form markup, not your account settings. Here is the exact order to check to find the cause in minutes.

· The SimpleForm Team

Your form looks fine. The fetch request fires, then the browser console shows 400 Bad Request and nothing arrives in your inbox. You check your HTML and it looks right, so you are left guessing what the endpoint did not like about the request. Every minute spent guessing is a real visitor's message sitting in a state you cannot see, and on a contact form that usually means a lead you will never know you lost.

SimpleForm is a hosted form backend for static websites: you point a plain HTML form's action attribute at a SimpleForm endpoint and submissions arrive by email and in your dashboard with no server code on your side. A 400 Bad Request is SimpleForm telling you the request reached the endpoint but the data in it was missing, malformed, or in a shape the endpoint could not parse — the fix is almost always in your form markup or request headers, not in your SimpleForm dashboard settings.

What Causes a 400 Bad Request on a SimpleForm Endpoint?

A 400 means the endpoint received your POST but could not read the fields it needs. The three most common causes are a missing name attribute on a required input, a form whose enctype does not match its content — for example sending a file without multipart/form-data — and a JSON request sent without a proper Content-Type: application/json header.

A less common but frequent cause is JavaScript that builds the request body incorrectly, such as sending a plain object to fetch instead of a FormData instance or a JSON string. A browser extension or corporate proxy that strips part of the request body before it leaves the page can produce the same symptom, even though your markup is correct.

This shows up most often right after a form gets rebuilt. A form that worked fine as a plain HTML submission returns a 400 the moment someone converts it to an AJAX call, because the JavaScript layer now has to reproduce everything the browser used to do automatically — build the body, set the right header, and keep the enctype consistent with what is actually being sent.

How Do You Debug a 400 Error Step by Step?

Work through the request itself before touching your SimpleForm dashboard settings. The order below finds the cause in most cases within a few minutes.

  1. Open your browser's Network tab and find the failed POST request.
  2. Check the request payload and confirm every required field has a non-empty name attribute and a value.
  3. Check that your form's enctype matches its content — plain fields need no enctype or application/x-www-form-urlencoded, file inputs need multipart/form-data.
  4. If you submit through fetch, confirm you are sending a FormData object or a JSON string, not a plain JavaScript object.
  5. If you set Content-Type manually on a FormData request, remove it, since the browser needs to set its own boundary string.
  6. Re-test with a minimal curl request against the same endpoint token to rule out anything client-side.

A minimal curl test isolates the endpoint from your frontend code entirely:

curl -X POST https://simpleform.dev/f/YOUR_TOKEN -d "name=Test User" -d "email=test@example.com" -d "message=Hello"

If that curl request succeeds and your browser form still fails, the problem is in your HTML or JavaScript, not your SimpleForm configuration. The MDN FormData documentation notes that a manually set Content-Type header on a multipart request overwrites the browser's own boundary string, which is the most common cause of a 400 in a fetch-based form that used to work as a plain HTML submission.

How Is a 400 Different From a 402, 403, or 429?

SimpleForm returns a distinct code for each failure reason, and the fix is different for each one. Confusing a 400 with a 429 wastes debugging time on the wrong layer of the request.

CodeMeaningWhere to look
400Missing or invalid form dataYour HTML fields and request body
402Plan submission limit exceededYour dashboard usage and plan tier
403Origin domain not on the allowed listThe allowed domains setting for the form
404Endpoint token not found or inactiveThe token in your form's action URL
429Rate limit exceededSubmission volume from one IP address

The rate limit behind a 429 is fixed at 10 submissions per IP per endpoint per hour on every plan. The submission limit behind a 402 depends on your plan — 100 a month on Free, 5,000 on Pro, 25,000 on Agency — and it resets on the first of the month. Neither of those is a 400: a 400 means the request itself was malformed, independent of how many submissions you have already sent.

How Do You Prevent 400 Errors Before They Ship?

Test the exact markup you are about to deploy, not a simplified version of it. Submit a real entry through the live page, including any file inputs, before you tell a client or teammate the form is done.

SimpleForm accepts three request shapes: standard URL-encoded POSTs, multipart form data for file uploads, and JSON payloads sent with an explicit Content-Type: application/json header. Pick one shape and keep your markup and your JavaScript consistent with it. Mixing a JSON fetch call with a form tag that still has enctype="multipart/form-data" is the single most common cause of a 400 in a contact form that gets rebuilt from a plain HTML submission into an AJAX one.

A correctly formed JSON submission sets the header and stringifies the body — skipping either step is the fastest way to turn a working form into a broken one:

fetch(form.action, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ name, email, message }) });

Once your markup is correct, SimpleForm's dashboard shows you the request payload each time a submission fails, so a future 400 does not turn into repeated guessing. The documentation lists the exact field names and headers each request shape expects, worth keeping open next to your editor while you rebuild a form.

Is This Worth Switching Form Backends Over?

If you already have a working hand-rolled backend, one 400 error is not a reason to migrate. But if you are redeploying a PHP mailer or a serverless function every time a client asks for a new field or a new recipient, the real cost is not the error message — it is the deploy cycle around every small change. SimpleForm's configuration lives in the dashboard, covered on the pricing page, so a new field or a new recipient address is a settings change instead of a code change.

Fix the Error, Then Confirm It for Good

Fix the field names, the enctype, or the missing header using the steps above, then run one more real submission through the live page to confirm it lands in your inbox. If you do not have a SimpleForm endpoint yet, create a free account and test against a real token — the free plan includes 100 submissions a month with no credit card, enough to confirm your fix before you point a production form at it.

Frequently asked questions

A 400 means SimpleForm received your POST request but could not read the fields it expected — usually a missing required field name, a mismatched enctype on a file upload, or a JSON request sent without a Content-Type: application/json header. Check the request payload in your browser's Network tab first; if the fields look correct, test the same data with curl to rule out anything client-side.

No. A 400 means the request body itself was malformed or incomplete, while a 429 means the request was well-formed but you sent more than 10 submissions from the same IP address to the same endpoint within an hour. Fixing a 400 means fixing your form markup; fixing a 429 means waiting for the hourly window to reset or reducing submission volume.

No. SimpleForm only counts a submission toward your plan's monthly limit — 100 on Free, 5,000 on Pro, 25,000 on Agency — once it accepts and stores the data. A rejected 400 request never reaches storage, so it does not use up any of your quota for the month.

Yes. Some privacy extensions and corporate proxies strip parts of a request body or block certain POST requests before they leave the browser. Test the same form in a private browsing window with extensions disabled; if the submission succeeds there, an extension on your original browser was interfering with the request, not SimpleForm.

No. A plain HTML form submitted the normal way sets its own Content-Type automatically based on the form's enctype attribute. You only need to set the header manually when you submit through fetch with a JSON body, and even then never set it alongside a FormData body, since the browser needs to add its own multipart boundary string.

Check what changed in the HTML — a renamed input, a removed name attribute, or a new file input added without switching enctype to multipart/form-data are the most common regressions. Compare the current markup against your last known-working deploy before assuming SimpleForm's configuration changed anything on its own.

Ship a working form in five minutes. Point your form's action at a SimpleForm endpoint and submissions land in your inbox and dashboard straight away. Start free or read the docs.

More from the blog