Get an endpoint Sign in

Docs AJAX / fetch

Bay 04JSON in, JSON out

#AJAX / fetch

Want to stay on the page and show your own message? Send the form with fetch and an Accept: application/json header. Instead of a redirect you get JSON back, with a proper HTTP status code. It works with any framework, or none.

contact.js
const form = document.querySelector("#contact");

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  const res = await fetch("https://sendm8.com/f/k3x9q2m7ab", {
    method: "POST",
    body: new FormData(form),
    headers: { Accept: "application/json" },
  });
  const result = await res.json();
  if (result.ok) form.replaceWith("Thanks! It's on its way.");
  else alert(result.error.message);
});

What comes back

Success

200 OK
HTTP/1.1 200 OK
Content-Type: application/json

{
  "ok": true,
  "id": "01K53N7XQ2R8M4DE7Q2K9F4X3M"
}

Error

4xx
HTTP/1.1 429 Too Many Requests
Content-Type: application/json

{
  "ok": false,
  "error": {
    "code": "quota_exceeded",
    "message": "This form has reached its monthly submission limit."
  }
}

ok is always there, so checking it is enough. The id is the submission’s id in your dashboard. On failure, error.message is a plain-English sentence you can show to your visitor as it is, and error.code (like quota_exceeded or rate_limited) stays the same for your code to check.

HTTP status codes
StatusWhat it means
200Stored. A 2xx always means the submission is safely saved.
303Browser posts only: redirect to your thank-you page, or to the Turnstile challenge page.
400We couldn’t read it: bad JSON, more than 100 fields, or a body over 64 KB without files.
403The page it came from isn’t on the form’s allowed domains, or a Turnstile check failed.
404No form with that id. Check for a typo in the URL.
410The form has been disabled by its owner.
413A file is over 5 MB, or the files add up to more than 10 MB.
423The form is paused. Nothing is stored until it’s switched back on.
429Too many too fast (10 a minute per visitor), or the form’s monthly cap is used up.