By default, your visitors leave your page when they send the form, and land on your redirect URL. If you'd rather keep them on the page, send the form with fetch:
<form id="contact" action="https://fwdr.io/f/YOUR_TOKEN" method="POST">
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>
<p id="contact-status" hidden></p>
<script>
const form = document.getElementById("contact");
const status = document.getElementById("contact-status");
form.addEventListener("submit", async (event) => {
event.preventDefault();
try {
const response = await fetch(form.action, {
method: "POST",
body: new FormData(form),
});
const { error } = await response.json();
status.textContent = error ?? "Thanks! Your message is on its way.";
if (!error) form.reset();
} catch {
status.textContent = "Something went wrong, please try again.";
}
status.hidden = false;
});
</script>
FormData takes care of files and captchas for you. If you prefer, you can also send JSON, with a Content-Type: application/json header.
When the form is sent from a script, we don't redirect: we answer in JSON instead.
| Status | Answer |
|---|---|
200 | {"ok": true}: got it. Messages caught by the captcha get the same answer, so bots don't learn anything. |
400 | {"error": "Your message is empty."} |
404 | {"error": "This form does not exist, or it was deleted."} |
422 | {"error": "You can attach up to 10 files, adding up to less than 25 MB."} |
Error messages are written for your visitors, so you can show them as is.
Browsers tell us when a request comes from a script. Other tools don't, so add an Accept: application/json header to get the answers above:
curl -X POST https://fwdr.io/f/YOUR_TOKEN \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"email": "[email protected]", "message": "Hello!"}'
Without it, we answer like we do to a regular form: a 302 redirect to your redirect URL. Nested objects and arrays are handled like grouped fields.