Guides

How to Test a SimpleForm Contact Form on Localhost

Static form backends often reject localhost by default. Here's how SimpleForm's origin rules actually work, and how to test a form endpoint before you push it live.

· The SimpleForm Team

You're building a static site locally on http://localhost:3000 or by double-clicking an index.html file, and the contact form needs to actually work before you push it. Pointing a form's action attribute at a hosted backend while you're still on localhost feels risky: will the request even leave your machine? Will the service reject it because the domain doesn't match? SimpleForm is a hosted form backend that receives submissions from any HTML form and emails or stores them without you writing server code, and its origin handling is built around exactly this situation. The short answer is that testing from localhost works out of the box, and the rest of this article walks through why, plus the two response codes that explain almost every local testing failure.

What Happens When You Submit a Form From Localhost?

A plain HTML form with method="POST" and its action attribute set to a SimpleForm endpoint URL works the same whether the page is served from localhost, a file path, or your production domain. The browser sends the request to SimpleForm's servers directly; nothing about the request depends on where the HTML happens to be sitting. What changes between environments is the Origin header the browser attaches to the request, and that header is the only thing SimpleForm uses to decide whether to accept or reject a submission.

How Does SimpleForm Decide Which Origins to Allow?

Every form has an optional "allowed domains" setting in the dashboard: a list of origins, one per line, that the form will accept submissions from. If you leave it blank, any origin is accepted. If you fill it in with your production domain only, a request from a different origin is rejected with a 403 response. The detail that matters for local testing is what happens when there's no Origin header at all: SimpleForm still accepts the request. Privacy-focused browsers, some proxies, and a number of local dev setups strip the header entirely, and rather than block those submissions, SimpleForm treats a missing header as passing. That's also, incidentally, why a stripped header in production never silently costs you a lead.

How Do You Test a Form Endpoint Before You Deploy?

You don't need a second, disposable endpoint for local testing. The same token you'll use in production works from localhost, as long as you haven't locked the form down to a single allowed domain yet.

  1. Create the form in your dashboard and copy its endpoint URL, something like https://simpleform.dev/f/abc123xyz.
  2. Set that URL as the action attribute on your local HTML form, and run your site the way you normally do — a dev server, or the file opened directly.
  3. Leave the "allowed domains" field empty for now, so both your localhost origin and your eventual production domain can reach the endpoint.
  4. Submit a real test entry with a name, email, and message, then check your inbox and the submissions list in the dashboard.
  5. Once you've confirmed the fields arrive intact, add your production domain to "allowed domains" and submit the same test again after you deploy, to confirm the locked-down form still accepts your real site.

Local Testing vs. Production Testing: What's the Difference?

The request path is identical; only the origin and, usually, your intent are different. The table below lays out what actually changes.

AspectLocalhost testingProduction
Origin header sentOften missing or a localhost valueYour real domain
Allowed domains settingLeave blank, or include the dev originYour production domain only
Redirect URLPoints nowhere useful yetSet to a real thank-you page
Rate limitSame 10 submissions per IP per hourSame 10 submissions per IP per hour

Notice the rate limit row: SimpleForm doesn't distinguish a "test mode" from a "live mode". Every submission, wherever it comes from, counts against the same IP-based rate limit and the same monthly plan limit. That's a deliberate simplicity trade-off, and it's the point where hand-rolling your own mock backend stops being worth it — you'd be rebuilding honeypot handling, rate limiting, and email delivery just to get a realistic test. SimpleForm's documentation covers each of those pieces in more detail if you want to see the exact request and response shapes before you wire anything up.

How Do You Test AJAX Submissions Locally?

If your form uses fetch instead of a normal page redirect, local testing works the same way, with one extra header. Add Accept: application/json to the request, and the endpoint returns a JSON body instead of issuing a redirect, which is what lets you show a success message without reloading the page:

document.querySelector('#myForm').addEventListener('submit', async (e) => {
  e.preventDefault();
  const form = e.target;
  const res = await fetch(form.action, {
    method: 'POST',
    headers: { 'Accept': 'application/json' },
    body: new FormData(form),
  });
  const data = await res.json();
  if (data.success) form.reset();
});

This is worth testing locally specifically, not just assuming it matches your redirect-based test, because a missing or wrong Accept header is the most common reason a developer sees a blank page or a browser download prompt instead of the JSON response they expected. Confirm the header is set correctly, and check the response in your browser's network tab, before you assume the endpoint itself is broken.

What If Your Local Requests Get Rejected?

Two response codes explain almost every local testing failure. A 403 means the form's allowed domains list is set and your current origin isn't on it — add the origin or clear the list while you're testing. A 429 means you've hit the rate limit of 10 submissions per IP per endpoint per hour, which is easy to do by resubmitting the same test form repeatedly while debugging. Wait out the hour or create a second form for testing if you expect to submit that often. A 404 usually means the token in your action attribute was copied wrong or the form was deleted.

Is It Safe to Point Local Testing at My Production Endpoint?

The honest objection here is that testing against your real endpoint means test submissions land in the same inbox and dashboard as real leads, and on the Free plan you only get 100 submissions and 3 forms a month to work with. If that mixing bothers you, create a second form in the dashboard specifically for testing — it costs nothing extra on any plan and keeps your test data separate — and delete or ignore it once your production form is confirmed working. What you don't need is a separate mock backend or staging service just to prove the request shape is correct, since the real endpoint behaves identically in both places. The same logic applies if your form includes a file input or a Pro-plan webhook: test them against the real endpoint too, because a mock backend won't tell you whether your multipart encoding or your webhook's signature verification actually works.

Getting Your Form Live

Once your local test submission shows up with the right fields, add your honeypot input (any field name starting with an underscore, such as _honeypot), set your production domain in allowed domains, and deploy. If you haven't created your first form yet, sign up for a free SimpleForm account — it takes about a minute, and the same endpoint URL you test with locally is the one your live site will use.

Frequently asked questions

No. SimpleForm accepts a request with no Origin header at all, which is common from local dev servers and privacy-focused browsers, so you can submit a real test from localhost using the same endpoint URL you'll use in production.

Yes, if you've already added your production domain to a form's allowed domains list, a request from a different origin, including localhost, returns a 403 response. Leave the list blank or add your dev origin while you're still testing.

Yes. SimpleForm doesn't have a separate test mode, so every accepted submission, local or live, counts toward your plan's monthly limit and the 10-per-hour, per-IP rate limit on that endpoint.

Any input whose name starts with an underscore, such as _honeypot or _gotcha, is treated as a spam trap. Include one hidden field with that naming pattern in your local test form so your production behavior matches what you tested.

Yes, as long as allowed domains is left blank or includes both origins. If you'd rather keep test submissions out of your real inbox, create a second form in the dashboard just for testing at no extra cost.

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