CORS errors on form submit mean the browser blocked a cross-origin POST because the form API did not return Access-Control-Allow-Origin for your site. Fix by adding your exact site URL to the form allowlist, using the vendor embed (which handles tokens), or submitting server-side with an ingest secret.

Guide

Fixing CORS errors on form submit

Why browser form POSTs fail with Access-Control-Allow-Origin errors, and how allowlists, embed scripts, Server Actions, and ingest secrets fix CORS on contact forms.

By Souhayb Kamal Dine

CORS fixes by setup

SetupFix
embed.jsAllow production + localhost origins on the form
Hand-rolled fetchSame allowlist + valid submit token from schema GET
Server ActionNo browser CORS—secret on server only
React NativeNo CORS—use ingest secret headers

You wired a contact form, opened DevTools, and see red text: blocked by CORS policy or No Access-Control-Allow-Origin. The submission never reached your inbox. This is one of the most common integration failures on modern Jamstack sites—and it is fixable once you know browsers enforce origin, not your feelings about www.

This guide explains why CORS happens on form POSTs, how preflight works, and the four fixes teams actually use with hosted backends like MuxForms.

What CORS is (in one paragraph)

Browsers restrict JavaScript running on https://www.example.com from reading responses from https://api.other.com unless api.other.com explicitly allows https://www.example.com via Access-Control-Allow-Origin. Pure HTML <form action="https://api.other.com"> navigations are different; fetch and XHR from your React app trigger CORS. Embeds that POST JSON also trigger OPTIONS preflight.

Symptom checklist

Console messageLikely cause
CORS policy blockedOrigin not allowlisted on form
Network shows 403 on POSTOrigin, missing token, or ingest auth
OPTIONS failsServer did not return CORS headers
Works locally, fails in prodlocalhost allowed but prod URL missing
Works on apex, fails on wwwTwo different origins

Fix 1: Add the exact origin to the allowlist

In MuxForms (and similar products), each form has allowed origins:

  • https://www.example.com
  • http://localhost:3000 for dev

Rules:

  • Include scheme (https://).
  • www and non-www are different—allow both or redirect consistently.
  • Staging URLs need their own entries (https://staging.example.com).

After saving, hard-refresh and retry. Read the JSON body on 403—MuxForms returns Origin is not authorized for this form when CORS blocks you.

Fix 2: Use the official embed

embed.js performs schema GET, obtains a submit token, and POSTs with headers the API expects. Custom fetch without that flow often dies on 403 even when CORS headers exist.

<div data-form-id="FORM_UUID"></div>
<script src="https://www.muxforms.com/embed.js" defer></script>

Next.js wrapper: blog tutorial.

Fix 3: Submit from the server (no CORS)

Server Actions, Route Handlers, and PHP wp_remote_post are not browsers—CORS does not apply. Send the ingest secret:

POST /api/v1/forms/{id}/submit
Content-Type: application/json
X-MuxForms-Ingest-Secret: ingsec_xxxxxxxx

Documented in submit API. This is the right pattern when you refuse to maintain origin lists for every preview deployment—proxy through your server.

Fix 4: React Native and Expo

Native fetch does not send browser Origin the same way; CORS is rarely the issue. 403 usually means missing ingest secret or wrong field names (422). See collecting forms from React Native.

Expo Web is a browser—allowlist its dev origin.

Preflight in depth

When JavaScript sets Content-Type: application/json, the browser sends:

OPTIONS /api/v1/forms/{id}/submit
Origin: https://www.example.com
Access-Control-Request-Method: POST

The server must respond 204 with:

Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: POST, OPTIONS

MuxForms handles OPTIONS when ingest is configured. If preflight fails, fix allowlist before debugging POST body.

Status codes vs CORS

CodeMeaningFix
403Auth / origin / token / TurnstileAllowlist, secret, CAPTCHA
422ValidationField names and lengths
429Rate limitSlow down tests
413Body > 64 KBShorten message

CORS errors sometimes mask 403—check Network tab response headers and body.

Common mistakes

  1. Allowing example.com but site redirects to www.example.com.
  2. Testing from Vercel preview URL without adding *.vercel.app or specific preview origin.
  3. Exposing ingest secret in client bundle—use server proxy instead.
  4. Forgetting honeypot _hp empty string in JSON payloads.

Related content

Common questions

Why OPTIONS preflight?
JSON Content-Type from JavaScript triggers preflight; server must respond 204 with CORS headers.
403 vs CORS error?
Disallowed origin may show as CORS in DevTools; response body often says origin not authorized.
www vs non-www?
They are different origins—allow both or pick one canonical URL.
MuxForms docs?
/docs/api and /docs/embed describe allowlists and tokens.

Apply this on your stack.

MuxForms is the inbox, embed, and JSON API. Start free, or upgrade to Pro or Team.

Upgrade to Pro