Nuxt contact forms
without API clutter.
Use Nuxt runtime config and $fetch while Form Plume runs the hosted backend for email notifications, submissions, spam filtering, uploads, integrations, and webhooks.
const config = useRuntimeConfig();
const status = ref("idle");
async function submit(event: Event) {
const form = event.currentTarget as HTMLFormElement;
status.value = "sending";
await $fetch(config.public.formPlumeEndpoint, {
method: "POST",
body: new FormData(form),
});
status.value = "sent";
}The full guide
A Nuxt-ready backend for contact forms.
Use public runtime config carefully, choose component or server route submission, and leave the form backend to Form Plume.
- 1Create your Form Plume accountStart free and give your Nuxt form a hosted endpoint for email, submissions, spam filtering, uploads, integrations, and webhooks.Start free
- 2Create the form and copy the endpointCopy the endpoint URL from Form Plume. It looks like
https://api.formplume.com/f/your-slug - 3Connect it inside NuxtUse runtime config for the public endpoint and submit FormData from a Nuxt component or server route. Send one test submission from the real Nuxt app or preview environment and confirm it reaches Form Plume.
You can use this AI prompt to hand the platform-specific wiring to an assistant without accidentally creating a backend.
Public runtime config
In Nuxt 3, public runtime config is the right place for a browser-visible Form Plume endpoint.
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
public: {
formPlumeEndpoint: process.env.NUXT_PUBLIC_FORM_PLUME_ENDPOINT,
},
},
});The endpoint can be public. Private email provider keys and webhook secrets should not be shipped to the client, and you do not need them for a Form Plume contact form.
Nuxt component with $fetch
<script setup lang="ts">
const config = useRuntimeConfig();
const status = ref<"idle" | "sending" | "sent" | "error">("idle");
async function submit(event: Event) {
const form = event.currentTarget as HTMLFormElement;
status.value = "sending";
try {
await $fetch(config.public.formPlumeEndpoint, {
method: "POST",
headers: { Accept: "application/json" },
body: new FormData(form),
});
form.reset();
status.value = "sent";
} catch {
status.value = "error";
}
}
</script>
<template>
<form @submit.prevent="submit">
<label for="email">Email</label>
<input id="email" type="email" name="email" autocomplete="email" required>
<label for="message">Message</label>
<textarea id="message" name="message" rows="5" required />
<button :disabled="status === 'sending'">
{{ status === "sending" ? "Sending..." : "Send message" }}
</button>
<p v-if="status === 'sent'" role="status">Thanks, we got your message.</p>
<p v-if="status === 'error'" role="alert">Please try again.</p>
</form>
</template>Form Plume receives the payload and handles email notifications, submissions dashboard storage, spam filtering, file uploads, integrations, and signed webhooks.
Server route option
Use a Nuxt server route when you want the server to validate extra fields or centralize redirects. Keep the route thin.
// server/api/contact.post.ts
export default defineEventHandler(async (event) => {
const formData = await readFormData(event);
const endpoint = "PASTE_YOUR_FORM_PLUME_ENDPOINT_HERE";
await $fetch(endpoint, {
method: "POST",
body: formData,
});
return { ok: true };
});Do not turn this into a mail service. Let Form Plume own the delivery and storage pipeline.
SSR and hydration caveats
Browser-only form submission should run in the submit handler, not during setup.
If you render a plain form action for no-JavaScript fallback, include _redirect so Form Plume can send visitors to a Nuxt thank-you page.
If your Nuxt site is deployed statically, the component submit pattern still works because the browser posts directly to Form Plume.
Uploads and integrations
Use FormData for uploads and avoid manually setting multipart headers.
After spam filtering, Form Plume can forward accepted submissions to Slack, Discord, Google Sheets, Mailchimp, ActiveCampaign, or your own signed webhook so Nuxt does not have to call each tool from the browser.
Primary sources
FAQ
Nuxt form questions
before deploy.
One line. Zero backend.
