← All frameworks

JavaScript framework

Svelte form handling without a backend

A standalone Svelte app (without SvelteKit's server) compiles to static assets, so there is no backend to receive a form. Formward fills that gap: your component posts the data to one EU-hosted URL and reads a JSON reply, no server code required.

The component below keeps a single status variable. The submit handler prevents the default navigation, sends FormData with fetch(), and updates status so the markup can react. Svelte's reactivity means the UI follows the variable with no extra wiring.

EU-hosted · Compiled Svelte ships anywhere, but every submission lands on an EU form backend — keeping personal data inside the EU/EEA by default.

The Svelte example

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

svelte

<script>
  let status = "idle"; // idle | sending | ok | error

  async function onSubmit(e) {
    status = "sending";
    const form = e.target;
    try {
      const res = await fetch("https://forms.formward.eu/f/<FORM_ID>", {
        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();
      status = "ok";
    } catch {
      status = "error";
    }
  }
</script>

{#if status === "ok"}
  <p>Thanksyour message is on its way.</p>
{:else}
  <form on:submit|preventDefault={onSubmit}>
    <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 disabled={status === "sending"}>
      {status === "sending" ? "Sending…" : "Send"}
    </button>
    {#if status === "error"}<p role="alert">Could not send. Try again.</p>{/if}
  </form>
{/if}

Success and error handling

  • on:submit|preventDefault is Svelte's shorthand for calling e.preventDefault(), so the fetch() takes over from the browser's default POST.
  • Reassigning status (not mutating it) is what triggers Svelte's reactivity — each state change re-renders the relevant block.
  • On an error status Formward returns { ok: false, error }; surface data.error if you want the exact reason (validation, plan limit, and so on).

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 Svelte submission

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

Svelte form handling without a backend | Formward