Guide
Vue contact form (no backend needed)
Vue is a client-side framework, but the contact form you ship doesn't need a backend on your side. Use the plain HTML form action in any Vue template, or a Vue 3 SFC with <script setup> and fetch for an inline success/error UX.
Use a plain HTML form
You do not need an API route. The form POSTs directly to Formhook, and your framework never has to touch the submission.
<form action="https://formhook.app/f/YOUR_API_KEY" method="POST">
<label>
Email
<input type="email" name="email" required />
</label>
<label>
Message
<textarea name="message" required></textarea>
</label>
<!-- honeypot: bots fill this; humans don't see it -->
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off" hidden />
<!-- optional: send users to a thank-you page after a native POST -->
<input type="hidden" name="_redirect" value="https://example.com/thanks" />
<button type="submit">Send</button>
</form>Or submit with fetch in Vue
Use this version if you want client-side success/error states without a full page reload.
<!-- src/components/ContactForm.vue -->
<script setup lang="ts">
import { ref } from "vue";
type Status = "idle" | "sending" | "ok" | "error";
const status = ref<Status>("idle");
async function onSubmit(e: Event) {
e.preventDefault();
status.value = "sending";
const form = e.target as HTMLFormElement;
const data = Object.fromEntries(new FormData(form));
const res = await fetch("https://formhook.app/f/YOUR_API_KEY", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
status.value = res.ok ? "ok" : "error";
if (res.ok) form.reset();
}
</script>
<template>
<p v-if="status === 'ok'">Thanks — we'll be in touch.</p>
<form v-else @submit="onSubmit">
<input type="email" name="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" required />
<input type="text" name="_gotcha" tabindex="-1" hidden />
<button type="submit" :disabled="status === 'sending'">
{{ status === "sending" ? "Sending…" : "Send" }}
</button>
<p v-if="status === 'error'">Something went wrong. Please try again.</p>
</form>
</template>How it works
The plain HTML form works inside any Vue template — it's just markup. For an inline success/error flow, the SFC snippet uses <script setup> with a ref-backed status state machine and a fetch call to formhook.app/f/YOUR_API_KEY. Object.fromEntries(new FormData(form)) serialized as JSON is the cleanest payload shape; Formhook also accepts form-encoded and multipart.
What happens next
- Submission lands in your Formhook dashboard within a second.
- Push notification fires on every device you've enabled it on.
- Reply directly from the dashboard (Pro and above) — particularly useful for SPA-only deploys with no email-sending capability.
Ship your Vue contact form today.
Sign up free, create a form, paste the API key. Five minutes.
Start freeFree tier: 5 forms · 250 submissions/month · No credit card.
Vue contact form — FAQ
- Do I need a backend or a Node server for this Vue contact form?
- No. The form POSTs to Formhook directly, so your Vue app can stay a pure static SPA. Build with Vite, deploy the dist/ folder to any CDN.
- Can I use this with Pinia / Vue Router state?
- Yes — the snippet is a self-contained SFC, but you can lift the status state into Pinia or use Vue Router to navigate to a separate thank-you route after success.
- What if I'm using Options API instead of <script setup>?
- The same fetch pattern works in a methods.onSubmit handler with data() { return { status: 'idle' } }. Only the syntax differs — Formhook doesn't care which Vue style you use.