splitforms.com
guide · ajax submission

Submit Form with JavaScript — Vanilla JS Reference

Submit a form with vanilla JavaScript — fetch + FormData + four-state status UI. No framework, no library, no jQuery. ~15 lines of code total.

html
<form id="contact">
  <input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
  <input name="name" required />
  <input name="email" type="email" required />
  <textarea name="message" required></textarea>
  <button id="submit" type="submit">Send</button>
</form>
<p id="status"></p>

<script>
const form = document.getElementById("contact");
const submitBtn = document.getElementById("submit");
const status = document.getElementById("status");

form.addEventListener("submit", async (e) => {
  e.preventDefault();
  submitBtn.disabled = true;
  status.textContent = "Sending…";

  try {
    const res = await fetch("https://splitforms.com/api/submit", {
      method: "POST",
      body: new FormData(form),
    });
    const data = await res.json();
    if (data.success) {
      status.textContent = "Thanks — your message is on its way.";
      form.reset();
    } else {
      status.textContent = data.message || "Something went wrong.";
    }
  } catch {
    status.textContent = "Network error — please try again.";
  } finally {
    submitBtn.disabled = false;
  }
});
</script>

Submitting a form with vanilla JavaScript means three things: intercept the submit event, POST via fetch, update the DOM based on the response. ~15 lines of code, no library, no framework. The pattern is the same whether your page is static HTML, React, Vue, or anything else — the form HTML and the JS handler don't care.

`form.addEventListener('submit', handler)` is the entry point. `e.preventDefault()` blocks the native form submission (which would navigate away from the page). `new FormData(form)` serializes every named field including file inputs. `await fetch(url, { method: 'POST', body: formData })` sends it.

Four states make the UI feel right: `idle` (the form is ready), `loading` (submission in flight), `ok` (success message visible), `err` (error message visible). Disable the submit button during `loading` so users can't double-click; re-enable on response (success or failure) in a `finally` block.

splitforms's `/api/submit` returns JSON: `{ success, message, submission_id }`. Parse with `res.json()`, check `data.success`, render the appropriate status. The endpoint sends permissive CORS headers so any frontend origin can POST without preflight gymnastics.

How to set this up

Step 01

addEventListener('submit', handler)

Attach a submit listener to the form. e.preventDefault() inside blocks native submission.

Step 02

new FormData(form)

Serializes every named field including file inputs. Works with any form structure.

Step 03

fetch with method POST and body FormData

fetch sets multipart/form-data Content-Type with the boundary automatically.

Step 04

Handle four states in the UI

idle, loading, ok, err. Disable submit during loading; re-enable in finally block.

15 lines, no library, four-state UI, no page reload.

Frequently asked questions

How do I submit a form using JavaScript?

Add a submit event listener to the form. Inside the handler, call e.preventDefault() to block native submission, then `fetch(url, { method: 'POST', body: new FormData(form) })`. Await the response, parse JSON, update the UI.

Why fetch instead of XMLHttpRequest?

Cleaner Promise-based API, native browser support since Chrome 42 / Firefox 39 (2015). XHR is still useful for upload progress events (which fetch doesn't support yet).

Do I need to set Content-Type when submitting form data with fetch?

No — fetch with a FormData body sets multipart/form-data automatically, including the boundary string. If you set it manually you'll break the boundary.

How do I disable the submit button during submission?

Set submitBtn.disabled = true at the start of the handler; reset to false in a finally block. Prevents double-clicks and gives the user visual feedback that the submission is in progress.

How do I handle network errors?

Wrap the fetch in try/catch. Catch fires on network failures (DNS, timeout, offline). Use res.ok or check the response status to handle HTTP errors (4xx, 5xx) — these don't throw with fetch.

Related guides

AJAX submission

Submit Form with AJAX — Working Code for 2026

AJAX submission

jQuery Submit Form — Legacy Codebase Reference

HTML forms

HTML Form Action — What It Does and How to Use It

Ship the form, not the backend.

Free for 1,000 submissions/month. Email delivery, AI spam filtering, signed webhooks, real dashboard — all on the free plan. No credit card.

Get a free access key →