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);
});contact.html
<form id="contact">
<label>Your email <input name="email" type="email" required></label>
<label>Message <textarea name="message" required></textarea></label>
<button>Send</button>
</form>contact.js
const res = await fetch("https://sendm8.com/f/k3x9q2m7ab", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
email: "alex@moreno.dev",
message: "Hiya! Are you free in October?",
}),
});
const result = await res.json();
// { ok: true, id: "01K53N7XQ2R8M4DE7Q2K9F4X3M" }What comes back
Success
HTTP/1.1 200 OK
Content-Type: application/json
{
"ok": true,
"id": "01K53N7XQ2R8M4DE7Q2K9F4X3M"
}Error
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.
| Status | What it means |
|---|---|
| 200 | Stored. A 2xx always means the submission is safely saved. |
| 303 | Browser posts only: redirect to your thank-you page, or to the Turnstile challenge page. |
| 400 | We couldn’t read it: bad JSON, more than 100 fields, or a body over 64 KB without files. |
| 403 | The page it came from isn’t on the form’s allowed domains, or a Turnstile check failed. |
| 404 | No form with that id. Check for a typo in the URL. |
| 410 | The form has been disabled by its owner. |
| 413 | A file is over 5 MB, or the files add up to more than 10 MB. |
| 423 | The form is paused. Nothing is stored until it’s switched back on. |
| 429 | Too many too fast (10 a minute per visitor), or the form’s monthly cap is used up. |