Skip to main content
New:Build with AI agents

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.

Free foreverNo credit cardNuxt endpoint ready in under a minute
ContactForm.vue
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.

  1. 1
    Create your Form Plume accountStart free and give your Nuxt form a hosted endpoint for email, submissions, spam filtering, uploads, integrations, and webhooks.Start free
  2. 2
    Create the form and copy the endpointCopy the endpoint URL from Form Plume. It looks like https://api.formplume.com/f/your-slug
  3. 3
    Connect 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.

Use runtimeConfig.public for a browser-visible Form Plume endpoint. Keep private service credentials out of public config.

One line. Zero backend.

The form backend you don’t have to build.

Free foreverNo credit cardSet up in under a minute