Skip to content
← All frameworks
vue · Vue

JavaScript framework

Vue form handling without a backend

Vue is a frontend framework, so a contact or signup form has no built-in place to send data. Rather than wiring up a Node server or a serverless function, you can submit straight to Formward - an EU-hosted form backend - and keep your Vue app entirely client-side.

The single-file component below uses the Composition API. A submit handler builds FormData from the form element, posts it with fetch(), and flips a reactive status ref so the template can show a spinner, a success message, or an error.

EU-hosted · Vue on the edge, data in Sweden: submissions are processed entirely within the EU, so a GDPR record of processing stays short and honest.

The Vue example

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

vue

<script setup>
import { ref } from "vue";

const status = ref("idle"); // idle | sending | ok | error

async function onSubmit(e) {
  status.value = "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.value = "ok";
  } catch {
    status.value = "error";
  }
}
</script>

<template>
  <p v-if="status === 'ok'">Thanks - we received your message.</p>
  <form v-else @submit.prevent="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>
    <p v-if="status === 'error'" role="alert">Could not send. Try again.</p>
  </form>
</template>

Success and error handling

  • @submit.prevent stops the native navigation so your fetch() handles the request and the user stays on the page.
  • Asking for Accept: application/json makes Formward reply with { ok: true, id, files }; without it the endpoint sends a 302 redirect suited to no-JS forms.
  • The hidden _gotcha honeypot must stay empty - Formward drops filled-in submissions as spam before they reach your inbox.

Spam protection

Every example above includes the _gotcha honeypot field. It is hidden from real users and must stay empty. When a bot fills it, Formward files the submission as spam and still answers with a normal success response, so the bot learns nothing and nothing reaches your inbox. Rate limits and the origin allowlist are checked on Formward's side as well. For a stricter gate, switch on Cloudflare Turnstile in the form's settings and add the widget; it adds a cf-turnstile-response field that Formward verifies on receipt. Turnstile is optional, and Cloudflare is a US provider, listed as such in the sub-processor register. The spam docs have the widget markup.

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": "3f2a9c1e-...", "files": 0, "success": { "title": "Thank you!", ... } }

files is the number of uploaded files stored and success carries the confirmation text from the form's settings. When you set a redirect URL for the form, the reply also has a redirect field your code can send the visitor to. On failure the body is { ok: false, error } with a 4xx status: 422 when required consent is missing or a field is too long, 403 when a Turnstile check fails, 429 for too many submissions from one address, 402 when the account is over its plan limit. See responses and redirects for the full table and the AJAX docs for CORS.

Other frameworks

Collect your first Vue submission

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

Vue contact form without a backend | Formward