splitforms.com

Contact form · Vue

Contact form for Vue.js websites

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.

  • 500 free / mo
  • 14ms latency
  • No backend code
Vue contact form — copy-paste code for splitforms

No backend code

No server, API route, or SDK. Your Vue form posts straight to one endpoint.

Straight to your inbox

Every submission is emailed to you and saved to a searchable dashboard — spam filtered before it reaches you.

Design without limits

It's your own Vue markup and styles. Splitforms is only the backend, so nothing constrains how the form looks.

Copy-paste ready

Your Vue contact form, ready to paste.

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.

Generate access key
contact.vue
<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

How to add a contact form to a Vue website.

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.

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.

Drop the Vue snippet into your project

Copy the Vue code example into your project and replace YOUR_ACCESS_KEY with the key from step 1.

Receive submissions in your dashboard

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

Where do your Vue form 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.

  • 500 submissions per month, free forever
  • Honeypot + AI spam classifier on every plan
  • Signed webhooks to Slack, Discord, your server
Vue contact form submissions in the splitforms dashboard

No backend needed

Do you need a backend for a Vue form? No.

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.

  • Composition API and Options API both supported
  • Works in Nuxt 3, Vue Vite, Vue CLI, and Vue 2 builds
  • Single-file component — copy, paste, ship
How splitforms processes a Vue form submission

Best practices

What a production-ready Vue form needs.

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

  • Use <script setup> syntax for the contact form. Half the boilerplate, full reactivity, no defineExpose needed.
  • Type your status ref: const status = ref<'idle' | 'loading' | 'ok' | 'err'>('idle'). TypeScript catches typos in your template's v-if blocks before they ship.
  • Set the access key via import.meta.env.VITE_SPLITFORMS_KEY (Vite) or useRuntimeConfig().public.splitformsKey (Nuxt) — never hardcode the literal string in your component file.
Production-ready Vue contact form best practices

How SplitForms works

From form to workflow in 3 simple steps.

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

A SplitForms contact form submission launching straight to your inbox

Add your endpoint

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

HTML form pointing at a SplitForms submit endpoint

Receive submissions

We instantly capture and organize every submission in your inbox.

Submissions inbox with searchable leads and status pills

Route anywhere

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

Generic integration tiles for email, sheets, chat, CRM, automate, and webhook

No credit card required. Set up in under 60 seconds.

Connect & automate

Connect your favorite tools and automate everything

SplitForms works with the destinations you route to and the platforms you build on — from Slack and Sheets to WordPress, Shopify, and Next.js.

Connect your SplitForms form to Slack, Google Sheets, Mailchimp, Zapier and more

Trusted by indie teams and agencies shipping forms worldwide

PETAL/COKRAFT.DELINEAR-XBUILD.DEVSTUDIO 71MERIDIANFRAME&CO

Testimonials

Loved by developers shipping at every scale.

40 quotes on record — from indie hacks to agency migrations.

Questions

Vue contact form questions.

View all FAQs
How do I add a contact form to Vue?

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.

Does splitforms work with Vue SSR / SSG?

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.

How do I handle form errors in Vue?

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.

Can I use splitforms with Vue 3 server functions / Nuxt server routes?

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.

How do I customize the success / redirect behavior?

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.

Does this work with Vuetify, Quasar, PrimeVue, or Element Plus?

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.

ref() doesn't auto-unwrap inside a template handler

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.

v-model and FormData read different sources

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.

@submit fires before .prevent unless you spell it right

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.

Vite env vars need the VITE_ prefix or they're undefined at build

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.

Vue 2 needs vue-fetch or axios — there's no built-in fetch wrapper

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.

reactive() unwraps inputs but breaks if you destructure the form object

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.

How does Vue handle forms without splitforms?

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.

Any deployment notes for shipping Vue to production?

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

Start free. Scale when you need more.

Choose a plan that fits your workflow — from a free form endpoint to full automations, exports, and higher submission limits.

Free

$0

Free forever

 
Best for testing

For side projects and indie devs.

  • 500 submissions / mo
  • Unlimited forms
  • Email notifications included
  • Honeypot spam filtering
  • Submissions dashboard
  • MCP setup stays free
  • No credit card required

3-Year

$59/ 36 months
was $99 · save 40% · new-user price

Pay $59. 3 years sorted.

  • 15,000 submissions / mo
  • Unlimited forms
  • Everything in Pro
  • Renews every 3 years
  • Long-term discount
  • Priority support included
  • Vote on the roadmap

No credit card required on Free • Cancel anytime