Vue Contact Form Without a Backend (Composition API)
A working Vue 3 contact form with no server and no API route: a plain-action version and a Composition API version with loading and success states. Nuxt notes included.
The ShipMyForm team
· 2 min read
A Vue contact form doesn't need a server. The form POSTs to a hosted endpoint; the service behind it stores the submission, screens the spam, and emails you. Your Vue app stays a static bundle — Vite build, any CDN, no API route, no nodemailer, no credentials in client code.
Two patterns cover it, both against the same endpoint.
Pattern 1: plain action, zero script
If the contact page just needs to work, HTML already does this:
<template>
<form action="https://shipmyform.com/f/YOUR_FORM_ID" method="POST">
<input type="text" name="name" placeholder="Your name" required />
<input type="email" name="email" placeholder="[email protected]" required />
<textarea name="message" placeholder="How can we help?" required></textarea>
<!-- honeypot: hidden from humans, filled by bots -->
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off" style="display:none" />
<input type="hidden" name="_redirect" value="https://yoursite.com/thanks" />
<button type="submit">Send</button>
</form>
</template>No script block at all. Field names become the fields you receive;
_redirect returns the visitor to your thank-you page.
Pattern 2: Composition API with status states
To stay on the page with a disabled button and inline confirmation:
<script setup>
import { ref } from "vue";
const status = ref("idle"); // idle | sending | sent | error
async function submit(e) {
status.value = "sending";
try {
const res = await fetch("https://shipmyform.com/f/YOUR_FORM_ID", {
method: "POST",
headers: { Accept: "application/json" },
body: new FormData(e.target),
});
status.value = res.ok ? "sent" : "error";
} catch {
status.value = "error";
}
}
</script>
<template>
<p v-if="status === 'sent'">Thanks — we'll get back to you soon.</p>
<form v-else @submit.prevent="submit">
<input type="text" name="name" placeholder="Your name" required />
<input type="email" name="email" placeholder="[email protected]" required />
<textarea name="message" placeholder="How can we help?" required></textarea>
<button type="submit" :disabled="status === 'sending'">
{{ status === "sending" ? "Sending…" : "Send" }}
</button>
<p v-if="status === 'error'" role="alert">Something went wrong — try again.</p>
</form>
</template>Notice what's absent: no v-model per field, no reactive form object.
new FormData(e.target) reads the DOM at submit time — v-model earns its
place only when something must react while the user types. The endpoint
returns JSON ({ ok: true }) because of the Accept header; error handling
in more depth lives in submit a form with fetch.
Nuxt, and the "just add a server route" question
On a static or SPA Nuxt site, the pattern above is the answer as-is. If you run the Nuxt server, you could proxy the form through a server route — but for a contact form it usually adds moving parts without capability: sending email still needs authenticated infrastructure, spam still needs screening, submissions still need to live somewhere. That's the whole job of a form backend, and as of 2026 ShipMyForm's free plan does it for 100 submissions a month — spam pipeline, searchable inbox, Slack/Sheets/webhook routing — with Vue-specific docs for the details.
Browser-side "email sending" packages embed a token that ships to every visitor — including the ones running scrapers. If a Vue tutorial puts an API key in a component, that key is public. POST to an endpoint; let secrets live server-side.
Next steps
- Compare with the React version — same two patterns, different framework accents.
- Generate the markup with your own fields (Vue output included): contact form generator.
- Create a free endpoint and the form above works as soon as you paste your form ID.
Frequently asked questions
- How do I make a contact form in Vue without a backend?
- POST the form to a hosted form endpoint instead of your own server. Either set the form's action attribute (works with zero JavaScript) or submit a FormData body with fetch inside your setup function for inline loading and success states. The endpoint stores the submission, filters spam, and emails you.
- How do I send Vue form data to my email?
- Through a form backend: your Vue app POSTs the fields, and the backend delivers them to your inbox from an authenticated sending domain. Avoid any package that sends email directly from the browser — its credentials ship in your bundle where anyone can extract and abuse them.
- Do I need v-model on every field?
- Not for submission. new FormData(formElement) collects values from any input with a name attribute at submit time, no reactive state required. Use v-model where you want live behavior — validation as the user types, character counters — and skip it everywhere else.
- Does this work with Nuxt?
- Yes. In a statically generated or SPA Nuxt site, the endpoint pattern is the standard answer. If you're running the Nuxt server anyway, you could proxy through a server route instead — but for a contact form that adds moving parts without adding capability, since spam filtering and delivery still have to live somewhere.
- How is spam handled without my own server?
- At the endpoint, before anything reaches you: honeypot checks, rate limiting, machine-learning classification, and a closer review of ambiguous messages. Server-side screening matters because bots POST directly to endpoints — client-side validation never slows them down.
Related guides
React Contact Form Without a Backend (Two Patterns)
No Express server, no API route, no email credentials in your client — two copy-paste patterns for a React contact form that actually delivers.
How to Submit a Form with JavaScript fetch()
The vanilla-JS pattern for submitting a form without a page reload — intercept submit, fetch() the data, and render success and per-field errors from the JSON response.
What Is a Form Endpoint? (And How to Use One)
The URL your form's action points to, explained: how endpoints receive submissions, how they relate to form backends, and how to wire one up.