Get your free splitforms access key
Sign up at splitforms.com, verify your email, and copy your access key from the dashboard. No credit card required.
Contact form · Vue
Composition API, Options API, Nuxt, Vite — every Vue setup works with one tiny single-file component. Submit a form, get email notifications, dashboard analytics, and webhooks. No backend, no SDK, no $30/mo plan.

No server, API route, or SDK. Your Vue form posts straight to one endpoint.
Every submission is emailed to you and saved to a searchable dashboard — spam filtered before it reaches you.
It's your own Vue markup and styles. Splitforms is only the backend, so nothing constrains how the form looks.
Copy-paste ready
Replace YOUR_ACCESS_KEY with the key from your dashboard — that's the whole integration. No SDK to install, no build step, just the vue you already write.
<script setup>
import { ref } from "vue";
const status = ref("idle");
async function onSubmit(e) {
status.value = "loading";
const formData = new FormData(e.target);
formData.append("access_key", "YOUR_ACCESS_KEY");
const res = await fetch("https://splitforms.com/api/submit", {
method: "POST",
body: formData,
});
const data = await res.json();
status.value = data.success ? "ok" : "err";
if (data.success) e.target.reset();
}
</script>
<template>
<form @submit.prevent="onSubmit">
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" required />
<input type="checkbox" name="botcheck" style="display:none" tabindex="-1" />
<button :disabled="status === 'loading'">
{{ status === 'loading' ? 'Sending…' : 'Send' }}
</button>
<p v-if="status === 'ok'">Thanks!</p>
<p v-if="status === 'err'">Error.</p>
</form>
</template>How to add it
To add a contact form to a Vue website you need three things: a free splitforms access key, the vue snippet above, and your key pasted into it. No backend, server, or SDK — the form posts to one URL and every submission lands in your inbox and dashboard.
Sign up at splitforms.com, verify your email, and copy your access key from the dashboard. No credit card required.
Copy the Vue code example into your project and replace YOUR_ACCESS_KEY with the key from step 1.
Submissions arrive in the splitforms dashboard within seconds. Free includes inbox delivery; Pro adds Slack, Discord, Sheets, or any signed webhook URL.
Where submissions go
Every submission is emailed to you and saved to a searchable dashboard — spam filtered before it ever reaches you. Search, export to CSV, or forward it to a webhook or Slack on Pro.

No backend needed
Your Vue form posts standard FormData to one URL. Splitforms validates the access key, runs the spam classifier, and forwards it to your email — so there's no server, API route, or database for you to build or maintain.

Best practices
The difference between a form that works in the demo and one that survives launch traffic — the production-tested defaults, in priority order.

How SplitForms works
Connect your form, collect every submission, and send data where it needs to go — without building backend infrastructure.

Point your form to your unique SplitForms endpoint. That's it.

We instantly capture and organize every submission in your inbox.

Send data to email, spreadsheets, CRMs, webhooks, and 7,000+ apps.

No credit card required. Set up in under 60 seconds.
Connect & automate
SplitForms works with the destinations you route to and the platforms you build on — from Slack and Sheets to WordPress, Shopify, and Next.js.

Trusted by indie teams and agencies shipping forms worldwide
Testimonials
40 quotes on record — from indie hacks to agency migrations.
“I replaced a Lambda + DynamoDB + SES contact form with six lines of HTML. It took eleven minutes, and the dashboard is better than what I was going to build.”
“We migrated 14 client sites off Formspree in a single weekend. The price is a third of what we paid, the API is more honest, and the spam filter actually works.”
“The webhook payload is signed, idempotent, and well-shaped. It reads like code from a competent team, not a CRUD app held together with duct tape.”
“I stopped reaching for Typeform on small marketing sites. splitforms covers 90% of the use case at none of the bloat.”
“I onboarded our whole agency in an afternoon. The MCP integration meant Cursor literally dropped the form straight into our client repos for us.”
“The free plan gave me 500 submissions before I paid a cent, and Pro is five dollars a month. I've spent more on coffee deciding which backend to use.”
“Spam went from forty junk entries a day to zero, with no reCAPTCHA puzzle ruining the form. The honeypot and time-trap just quietly do their job.”
“Point the form action at one endpoint and you're done. No SDK, no client library, no build step. This is how a form backend should feel.”
“Leads land in Slack the second someone submits, and a copy goes to Google Sheets for the sales team. I wired both up in under ten minutes.”
“I run a static Hugo site on a five-dollar VPS. splitforms gave it a real contact form without me standing up a single server.”
Questions
Drop the <script setup> single-file component above into any .vue file in your project, replace YOUR_ACCESS_KEY, and import it where you need it. No router config, no backend, no Vuex/Pinia required.
Yes. The form component is client-only (it uses fetch), but it hydrates cleanly into any SSR-rendered page. For Nuxt SSG, see the dedicated /forms/nuxt page — same backend, Nuxt-specific runtimeConfig pattern.
Use a status ref with four states: idle, loading, ok, err. Show inline messages with v-if for ok/err. The fetch call returns { success: boolean, message?: string } — render data.message for the user when success is false.
Yes. If you'd rather hide the access key, post from a Nuxt /server/api/contact.post.ts route or a Vue 3 server function. The Nuxt page covers this pattern in detail.
Two options. (1) Stay on-page and show a Vue-rendered success message (the default in our snippet). (2) Configure /thanks in Dashboard → Form settings → Redirect and use a native form submit. Submitted redirect fields are ignored.
Yes. Use the UI library's form/input components for the markup, but make sure each input has a name attribute (some component libraries omit it by default). FormData reads from name attributes, not v-model bindings.
If you write status === 'loading' inside <script setup> it works because Vue auto-unwraps refs in templates — but inside a function you need status.value. Mixing the two is the #1 cause of "why isn't my button disabling?" bugs in Vue contact forms.
If you bind inputs with v-model="name" to a ref and then build new FormData(e.target), FormData reads the DOM — which Vue keeps in sync — so it works. But if you bind a computed value and the input doesn't have a name attribute, FormData drops it silently. Always set name="…" on every input.
Vue's event modifier syntax is @submit.prevent="onSubmit". People often write @submit="onSubmit" and then forget to call e.preventDefault() inside the handler — the page reloads and the fetch is cancelled mid-flight.
Reading import.meta.env.SPLITFORMS_KEY returns undefined unless you rename it to VITE_SPLITFORMS_KEY (Vite) or expose it via runtimeConfig (Nuxt). Vite intentionally hides anything without the prefix to prevent leaking server secrets.
Modern browsers all support fetch, but if you support IE11 (Vue 2 territory), you need a polyfill or axios. Splitforms only requires a POST with FormData — any client lib works.
If you wrap your form fields in const form = reactive({ name: '', email: '', message: '' }) and then do const { name, email } = form to shorten template references, you lose reactivity — the destructured locals are plain primitives, not refs. The submit handler reads stale values and you get empty fields in the splitforms inbox. Use toRefs(form) when destructuring, or skip destructuring and reference form.name directly. Same trap exists for props passed from a parent.
Vue ships nothing form-related beyond v-model for two-way binding. To deliver a submission anywhere, you write the same backend stack as React: an Express/Nitro/Hono route, an email provider, a database, spam filtering. Nuxt narrows the gap with /server/api routes, but you're still operating the route. Pinia/Vuex don't help — they're for client state, not network. The Vue ecosystem has FormKit and VeeValidate for validation UX, but neither delivers submissions. Splitforms slots in as the missing endpoint: any Vue 2 / Vue 3 / Nuxt setup posts a FormData to one URL, gets { success: true } back, done.
Vue + Vite produces a static bundle that deploys anywhere. For Nuxt, every Nitro preset (Vercel, Netlify, Node, Cloudflare, static, AWS Lambda) works identically because the form posts client-side. On Cloudflare Pages with the Nuxt Cloudflare preset, avoid proxying through /server/api — the fetch round-trip eats the 10ms CPU budget on the free tier; post directly to splitforms. Vite-only (non-Nuxt) projects must use VITE_SPLITFORMS_KEY or env vars are silently undefined client-side. Nuxt uses runtimeConfig.public.splitformsKey. Both inline the value into the bundle, so domain-lock the key in the splitforms dashboard.
Simple pricing
Choose a plan that fits your workflow — from a free form endpoint to full automations, exports, and higher submission limits.
Free forever
For side projects and indie devs.
For agencies and growing products.
Pay $59. 3 years sorted.
No credit card required on Free • Cancel anytime