splitforms.com

Contact form · Nuxt

Contact form for Nuxt apps

Nuxt 3, Nuxt Content, Nuxt Studio, Nuxt UI — all flavors work. Use a Vue 3 Composition API page, pull your key from runtimeConfig.public, and skip writing a /server/api route. Or use a Nitro server route to keep the key server-side. Both patterns supported.

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

No backend code

No server, API route, or SDK. Your Nuxt 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 Nuxt markup and styles. Splitforms is only the backend, so nothing constrains how the form looks.

Copy-paste ready

Your Nuxt 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
<!-- pages/contact.vue -->
<script setup>
import { ref } from "vue";

const status = ref("idle");
// Expose the key via runtimeConfig.public in nuxt.config.ts
const config = useRuntimeConfig();

async function onSubmit(e) {
  status.value = "loading";
  const formData = new FormData(e.target);
  formData.append("access_key", config.public.splitformsKey);

  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 Nuxt website.

To add a contact form to a Nuxt 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 Nuxt snippet into your project

Copy the Nuxt 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 Nuxt 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
Nuxt contact form submissions in the splitforms dashboard

No backend needed

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

Your Nuxt 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.

  • Works with Nuxt 3 and any Nitro preset (Vercel, Netlify, Node, Cloudflare, static)
  • Access key pulled from runtimeConfig.public — never hardcoded
  • No /server/api route required (but supported via the alternative pattern)
How splitforms processes a Nuxt form submission

Best practices

What a production-ready Nuxt 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 the client-side fetch pattern (the snippet above) — fewer moving parts, works on every Nitro preset, no server CPU time used.
  • Define splitformsKey in nuxt.config.ts under runtimeConfig.public. Read from process.env.NUXT_PUBLIC_SPLITFORMS_KEY so it's environment-driven.
  • If you must hide the key, use a /server/api/contact.post.ts Nitro route — define the key under runtimeConfig (not .public) so it stays server-side.
Production-ready Nuxt 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. And with Stripe connected, your forms can take payments too.

Connect your SplitForms form to Slack, Google Sheets, Mailchimp, Zapier and more
NewTake payments on your formsDeposits, bookings, order forms — submitters check out through Stripe and the money goes straight to your Stripe account. Included on every paid plan.See plans →

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

Nuxt contact form questions.

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

Save the snippet above as pages/contact.vue. Add splitformsKey under runtimeConfig.public in nuxt.config.ts, populated from process.env.NUXT_PUBLIC_SPLITFORMS_KEY. Visit /contact — done. No /server/api route needed.

Does splitforms work with Nuxt SSR / SSG / hybrid rendering?

Yes — all three modes. The form is client-side (uses fetch in a setup-script handler), so it works regardless of the page's render mode. SSR and SSG just affect how the form's wrapper page is generated.

How do I handle form errors in Nuxt?

Use a status ref with four values: idle, loading, ok, err. Render error messages from the splitforms response inside v-if="status === 'err'". The fetch returns { success, message? } — use the message for user-facing errors.

Can I use splitforms with Nuxt server routes / Nitro instead?

Yes — see the alternative-pattern snippet. Define the key as private under runtimeConfig and proxy through server/api/contact.post.ts. Adds latency but keeps the key entirely server-side.

How do I customize the success / redirect behavior?

Two options. (1) Stay on-page with a Vue-rendered success message (default in our snippet). (2) After a successful fetch, call await navigateTo('/thanks'). For native form posts, configure the absolute thank-you URL in Dashboard → Form settings → Redirect.

Will splitforms work with Nuxt Content, Nuxt UI, or Nuxt Studio?

Yes — all three. Nuxt UI's <UForm> component works (it's just a styled <form>); Nuxt Content can embed the contact form via MDC syntax; Nuxt Studio's editor previews the form correctly. Same backend regardless.

runtimeConfig.public is required for client exposure — `runtimeConfig.x` is server-only

Nuxt's runtimeConfig has two scopes. Anything under runtimeConfig.public is exposed to the browser bundle; anything else is server-only. If you put splitformsKey directly in runtimeConfig (not .public), useRuntimeConfig().splitformsKey returns undefined client-side.

useRuntimeConfig() called outside setup() returns empty object

Calling useRuntimeConfig() inside a regular function (not inside <script setup> or a composable) returns {}. Always call it at the top of <script setup> and capture the value, then use config.public.splitformsKey inside event handlers.

Nitro Cloudflare preset has 10ms CPU time on the free plan

If you proxy the splitforms call through a Nuxt server route (/server/api/contact.post.ts), the fetch round-trip eats your Cloudflare Worker CPU budget. On Cloudflare Pages Free tier, this can fail under load. Skip the proxy: have the form POST directly to splitforms.com.

Nuxt Content's <ContentDoc> rendering can break form HTML

If you embed the form in a Markdown file rendered by Nuxt Content, MDC syntax may interpret your inputs as MDC components. Wrap the form in <NuxtContent> (or use :component="ContactForm") instead of inline form HTML.

useFetch / $fetch differ from native fetch on body handling

Nuxt's $fetch('/url', { body: formData }) automatically sets Content-Type: multipart/form-data AND tries to JSON-stringify your FormData. Use the native fetch() API in the form handler — $fetch is for typed Nitro routes, not third-party endpoints.

Payload extraction (Nuxt 3 SSG) inlines form state into the prerendered HTML

When you run nuxt generate for SSG, Nuxt's payload extractor serializes any reactive ref() values referenced during prerender into a __NUXT__ global on the static HTML — including the initial state of your form fields if they're populated server-side from useAsyncData. If you've prefilled a name ref from a logged-in user fixture, every visitor sees that fixture's value on first paint until hydration overrides it. Keep form refs initialized to empty strings on the server, or wrap the form in <ClientOnly> so SSG doesn't snapshot any default state at all.

How does Nuxt handle forms without splitforms?

Nuxt 3's Nitro server gives you /server/api/contact.post.ts for free — write a handler, parse FormData, send email, store the submission, fan out a webhook. The DX is good; the operational cost is the same as any framework: an SMTP integration (Nodemailer + a transactional provider), a Postgres or KV store for submissions, anti-spam logic (honeypot + classifier or hCaptcha), and webhook signing if you want secure delivery to Slack/Discord/Make.com. Each feature is another deploy artifact, another env var, another thing to monitor. Splitforms is the inverse: skip the /server/api route entirely (post directly from the page), or proxy through a one-line Nitro route to keep the key server-side.

Any deployment notes for shipping Nuxt to production?

Nuxt's Nitro server abstracts away deployment — pick a preset (vercel, netlify, cloudflare-pages, cloudflare-workers, node-server, static, aws-lambda, digital-ocean) and ship. The form's POST is cross-origin to splitforms, so the preset doesn't affect delivery. On Cloudflare Workers (10ms CPU on free tier), strongly prefer Pattern A — Pattern B's $fetch round-trip can blow the budget under load. Use runtimeConfig.public.splitformsKey (client-exposed) for Pattern A and runtimeConfig.splitformsKey (server-only) for Pattern B. Both populate from NUXT_PUBLIC_SPLITFORMS_KEY / NUXT_SPLITFORMS_KEY env vars.

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, Stripe payments, 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/ first 3 years
was $99 · save 40% · new-user price

Pay $59. 3 years sorted.

  • 15,000 submissions / mo
  • Unlimited forms
  • Everything in Pro
  • $59 for 3 years, then $59/year
  • Long-term discount
  • Priority support included
  • Vote on the roadmap

No credit card required on Free • Cancel anytime