← All frameworks

Vanilla JS

HTML form handling without a backend

A static HTML page has no server, so the moment you add a form you need somewhere for the data to go. Formward is that somewhere: set the form's action to your Formward endpoint and the browser handles the rest. No build step, no framework, no backend — just an HTML file you can host anywhere.

The first form below works with JavaScript disabled and is the whole solution for most static pages. The second snippet layers on a vanilla fetch() submit so you can keep the user on the page and show success or error inline when scripting is available.

EU-hosted · One HTML file plus an EU endpoint is the simplest GDPR-clean form you can build: no server to audit and no data leaving the EU/EEA.

The HTML example

Copy this into your project and replace <FORM_ID> with the id of a form you create in the Formward dashboard.

html

<!-- 1) No JavaScript required — a standard POST. -->
<form action="https://forms.formward.eu/f/<FORM_ID>" method="POST">
  <input type="email" name="email" required />
  <textarea name="message" required></textarea>
  <!-- Honeypot: leave empty. -->
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off"
    style="display:none" aria-hidden="true" />
  <button type="submit">Send</button>
</form>

<!-- 2) Optional vanilla-JS AJAX submit for inline feedback. -->
<form id="contact" action="https://forms.formward.eu/f/<FORM_ID>" method="POST">
  <input type="email" name="email" required />
  <textarea name="message" required></textarea>
  <input type="text" name="_gotcha" tabindex="-1" autocomplete="off"
    style="display:none" aria-hidden="true" />
  <button type="submit">Send</button>
  <p id="status" role="status"></p>
</form>
<script>
  const form = document.getElementById("contact");
  const out = document.getElementById("status");
  form.addEventListener("submit", async (e) => {
    e.preventDefault();
    out.textContent = "Sending…";
    try {
      const res = await fetch(form.action, {
        method: "POST",
        headers: { Accept: "application/json" },
        body: new FormData(form),
      });
      const data = await res.json(); // { ok: true, id, files }
      if (!res.ok || !data.ok) throw new Error(data.error || res.status);
      form.reset();
      out.textContent = "Thanks — message sent.";
    } catch {
      out.textContent = "Could not send. Please try again.";
    }
  });
</script>

Success and error handling

  • Form one needs nothing but HTML: the browser POSTs to Formward and follows the 302 redirect (or lands on your thank-you page).
  • Form two calls e.preventDefault() and sends Accept: application/json, so Formward returns { ok: true, id, files } and the visitor stays put.
  • FormData(form) serialises every named field, including file inputs — uploaded files come back in the files array of the JSON response.

Spam protection

Every example above includes the _gotcha honeypot field. It is hidden from real users and must stay empty; Formward silently drops any submission where it is filled, which stops most bots with no CAPTCHA. For a stricter gate, add a Cloudflare Turnstile widget and send its token as the cf-turnstile-response field — Formward verifies it on receipt.

The JSON response

A standard POST gets a 302 redirect (or your thank-you page). Send the Accept: application/json header — as every fetch() example above does — and Formward returns JSON instead:

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

{ "ok": true, "id": "clxyz123...", "files": [] }

On failure the body is { ok: false, error } with a 4xx status (validation, plan limit, and so on). See the AJAX docs for the full status-code table and CORS notes.

Other frameworks

Collect your first HTML submission

Create a form, paste the snippet, and keep every submission in the EU. GDPR-clean from the first POST.

HTML form handling without a backend | Formward