Blog
AJAX form submission: send a form without a page reload
· 4 min read
Submit an HTML form the plain way and the browser does what it has always done: navigate to a new page, or reload the current one, throwing away whatever was on screen a moment earlier. For a form that lives on its own page, that's harmless. For a form inside a modal, a chat widget, a checkout flow, or any single-page app, a full navigation is a jarring reset - the modal closes, the app remounts, the scroll position is gone. AJAX submission - sending the form data with JavaScript's fetch() instead of letting the browser navigate - fixes that, and it's a handful of lines.
Why skip the page reload
A full-page reload discards everything JavaScript was holding in memory: an open modal, a wizard's current step, unsaved state elsewhere on the page. If the form lives inside a React, Vue, or Svelte component tree, letting the browser navigate away also tears down the whole client-side app and reloads it from scratch.
Submitting through fetch() keeps the page - and whatever app is running on it - intact. You decide what happens next: show a success message inline, close the modal, reset the fields, redirect programmatically if you want to. It's the difference between the browser deciding and you deciding.
It's an enhancement, not a requirement
You don't have to choose one approach over the other. Leave the action and method attributes on the <form> tag exactly as they'd be for a plain POST, and only intercept the submit event with JavaScript when it's available. If a script fails to load, an extension interferes, or a visitor has JavaScript disabled, the form still works - it just falls back to a normal page navigation instead of an inline status message. That's the whole idea of progressive enhancement: the base case, an HTML form posting straight to https://formhook.app/f/{apiKey}, always works on its own. JavaScript only makes it nicer when it's there.
A minimal example
No framework needed - this is the whole pattern in vanilla JavaScript. It prevents the default navigation, sends the form data as FormData, and swaps in a status message depending on the result:
const form = document.querySelector("#contact-form");
form.addEventListener("submit", async (e) => {
e.preventDefault();
const res = await fetch(form.action, {
method: "POST",
body: new FormData(form),
headers: { Accept: "application/json" },
});
if (res.ok) {
form.reset();
document.querySelector("#form-status").textContent = "Thanks - we got it.";
} else {
document.querySelector("#form-status").textContent = "Something went wrong. Please try again.";
}
});Ask for JSON, not a redirect
Notice the Accept: application/json header in the example above. Without it, a form backend built to also serve plain HTML submissions might try to redirect the browser to a "thank you" page - correct behavior for a normal POST, but useless when you're calling it from fetch(). Sending Accept: application/json tells Formhook's endpoint that the request came from JavaScript, so it returns a JSON success or error response instead of a redirect, and your code can branch on res.ok the way the example does. See the docs for the exact request format.
Don't forget accessibility
Swapping in a status message with textContent is invisible to a screen reader unless the browser is told to announce it. Add aria-live="polite" to the status element (the one document.querySelector("#form-status") is targeting in the example) and assistive technology will read the update out loud the moment it changes - success or error - without the visitor needing to find and re-focus it themselves. It's one attribute, and it's the difference between a form that works for everyone and one that quietly excludes screen reader users the moment JavaScript takes over.
None of this requires a library or an SDK - a Formhook endpoint accepts a plain form POST from any host, and the fetch() version above is optional progressive enhancement on top of that, not a prerequisite. See the docs for the full request/response reference, and pricing for the free tier's limits.
Ship a working form in one line
EU-hosted, submissions kept forever, push notifications on every tier.
Start freeNo credit card · read the docs
Keep reading
- Send form submissions to Slack (no code required)Email alerts get buried in the inbox. Here's how to get website form submissions posted straight into a Slack channel - the DIY way and the zero-code way.
- Contact form best practices that actually increase conversionsEvery extra field is a chance to lose a visitor. Practical, opinionated guidance on field count, labels, mobile input types, honeypot spam protection, and success states.
- What is a form backend? (And when you need one)A form backend receives, stores, and forwards your website's form submissions so you don't run a server. Here's how they work and when you need one.