All resources
Static site forms

How to Fix CORS Errors When Submitting a Form

Getting 'blocked by CORS policy' when your form posts with fetch()? What the error means, why it happens, and how to fix it — including the no-JS escape hatch.

The ShipMyForm team

· 4 min read

You wired your form up with fetch(), hit submit, and the console throws:

text
Access to fetch at 'https://…/f/YOUR_FORM_ID' from origin 'https://yoursite.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is
present on the requested resource.

Your request probably did reach the server — the browser just refused to let your JavaScript read the response. This guide explains what that means, why it happens when a form posts to a different origin, and the handful of ways to fix it (including the one that sidesteps CORS entirely).

What CORS actually is

CORS (Cross-Origin Resource Sharing) is a browser rule: when your JavaScript fetch()es a URL on a different origin than the page, the browser only hands you the response if that server opts in with an Access-Control-Allow-Origin header naming your origin. Your form's action lives on shipmyform.com; your page lives on yoursite.com — different origin — so the check applies.

It's enforced by the browser, not the server. That's why the same request works fine from curl or your backend but fails in the browser.

The escape hatch: a plain form POST has no CORS

Here's the part people miss. CORS only applies to requests your script reads. A normal HTML form submit is a navigation — the browser posts and follows the response — so CORS never applies to it:

html
<form action="https://shipmyform.com/f/YOUR_FORM_ID" method="POST">
  <input name="email" type="email" required />
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
</form>

This works cross-origin, today, with zero configuration. If you don't strictly need an inline success state, this is the simplest "fix." You only meet CORS when you switch to fetch() to avoid a page reload — which is worth it for the UX, so here's how to get it right.

The real fix for fetch(): allow your origin

When you fetch() cross-origin, the server has to return your origin in Access-Control-Allow-Origin. A form backend decides that from its allowed-origins list. If your site isn't on it, the endpoint omits the header and the browser blocks the read — the exact error above.

With ShipMyForm: open the form's Settings → Allowed domains and add your site (e.g. yoursite.com). The endpoint then reflects your origin back in the CORS header and the fetch succeeds. Adding yoursite.com also covers its subdomains like www.yoursite.com.

js
const res = await fetch("https://shipmyform.com/f/YOUR_FORM_ID", {
  method: "POST",
  headers: { Accept: "application/json" },
  body: new FormData(form),
});
const data = await res.json(); // now readable: { ok: true } or a 422 with fields
Two ways the header gets set:

With no allowed domains configured, the endpoint returns Access-Control-Allow-Origin: * (any site can post) — convenient but not recommended for a real form. Once you list your domains, it reflects only those origins back, and a POST from anywhere else is rejected with a 403. So the fix is to list your site, not to open it up.

Preflights and JSON bodies

For some requests the browser sends a preflight — an OPTIONS request that asks permission before the real one. It's triggered by "non-simple" requests, most commonly a Content-Type: application/json body:

BodyContent-TypePreflight?
new FormData(form)multipart/form-dataNo (simple request)
URL-encoded stringapplication/x-www-form-urlencodedNo
JSON.stringify(...)application/jsonYes — an OPTIONS first

Accept: application/json is itself safelisted, so it never forces a preflight — only the JSON content type does. ShipMyForm answers the OPTIONS preflight automatically (with the same allowed-origin logic), so JSON works too — but if you want to avoid the extra round-trip, send a FormData body. It's also simpler and handles file inputs.

Common mistakes that cause (or fake) CORS errors

  • Origin not in the allowed list — the number-one cause. Add your exact domain. Watch http vs https and www vs apex.
  • Reaching for mode: 'no-cors' — this doesn't fix anything. It returns an opaque response: res.ok is always false-ish, res.json() throws, and you can't read { ok: true } or validation errors. Never use it here.
  • credentials: 'include' — a public form endpoint uses no cookies, so don't set this. Combined with a wildcard origin it actually causes a CORS failure.
  • Wrong URL — a typo'd form ID or an http:// endpoint on an https:// page (mixed content) surfaces as a network/CORS-looking error.
  • Assuming the POST failed — a CORS block happens after a successful request in many cases; your submission may already be stored. Fix the header, don't resubmit blindly.

Quick checklist

  1. Submitting with fetch()? Add your site to the form's allowed domains.
  2. Prefer a FormData body to skip the preflight.
  3. Never use mode: 'no-cors' or credentials: 'include'.
  4. Just want it working with no JS? Use a plain <form action> — no CORS at all.

Next steps

Frequently asked questions

Why does my form submission get a CORS error?
The browser blocks a cross-origin fetch() when the server's response doesn't include an Access-Control-Allow-Origin header matching your site. With a form backend, that usually means your site's domain isn't on the form's allowed-origins list, so the endpoint won't return your origin in that header.
Do plain HTML forms have CORS problems?
No. A normal <form method="POST" action="…"> submit is a navigation, not a fetch your JavaScript reads, so the browser doesn't apply CORS to it. It works cross-origin regardless. CORS only comes into play when you submit with fetch() or XMLHttpRequest and read the response.
Should I use mode: 'no-cors' to fix it?
No. mode: 'no-cors' doesn't grant access — it makes the response opaque, so you can't read the JSON (result.ok, validation fields, etc.) and can't tell success from failure. Fix the allowed origins instead, or use a plain form POST.
Does sending JSON trigger a CORS preflight?
Yes. A Content-Type of application/json isn't CORS-safelisted, so the browser sends a preflight OPTIONS request first. Sending a FormData body instead keeps it a simple request with no preflight. Either works with ShipMyForm as long as your origin is allowed — it answers the preflight automatically.

Related guides