Skip to main content
New:Build with AI agents

JavaScript contact forms
without backend code.

Enhance a real HTML form with fetch while Form Plume handles email notifications, stored submissions, spam filtering, uploads, integrations, and signed webhooks.

Free foreverNo credit cardJavaScript endpoint ready in under a minute
contact.js
const form = document.querySelector("#contact");
const status = document.querySelector("#status");

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  status.textContent = "Sending...";

  const response = await fetch(form.action, {
    method: "POST",
    headers: { Accept: "application/json" },
    body: new FormData(form),
  });

  status.textContent = response.ok
    ? "Thanks, we got your message."
    : "Please try again.";
});

The full guide

Progressive enhancement for JavaScript contact forms.

Use the guide below to keep the fallback simple, add inline status with fetch, and let Form Plume run the hosted backend.

  1. 1
    Create your Form Plume accountStart free and give your JavaScript 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 JavaScriptKeep the real form action in the HTML, then add fetch as an enhancement for inline success and error states. Send one test submission from the real JavaScript 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.

Start with a form that works without JavaScript

The safest JavaScript contact form begins as ordinary HTML. The action points to Form Plume, method="POST" sends the fields, and the browser can still submit if your script fails to load.

<form id="contact-form" action="PASTE_YOUR_FORM_PLUME_ENDPOINT_HERE" method="POST">
  <label for="name">Name</label>
  <input id="name" name="name" autocomplete="name" required>
 
  <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></textarea>
 
  <button type="submit">Send message</button>
  <p id="contact-status" role="status"></p>
</form>

That one endpoint gives the static page a hosted backend: email notifications, a submissions dashboard, spam filtering, file upload handling, integrations, and signed webhooks without PHP, serverless functions, SMTP, or a database.

Add fetch as progressive enhancement

Only intercept submit after you have a real form. Send FormData so files and custom fields keep working as the form grows.

const form = document.querySelector("#contact-form");
const status = document.querySelector("#contact-status");
 
form?.addEventListener("submit", async (event) => {
  event.preventDefault();
  status.textContent = "Sending...";
 
  try {
    const response = await fetch(form.action, {
      method: "POST",
      headers: { Accept: "application/json" },
      body: new FormData(form),
    });
 
    if (!response.ok) {
      throw new Error("Submission failed");
    }
 
    form.reset();
    status.textContent = "Thanks, we got your message.";
  } catch {
    status.textContent = "We could not send this message. Please try again.";
  }
});

Put the endpoint on the form, not hidden inside the script. That preserves the no-JavaScript fallback and makes QA easier.

Redirect when JavaScript is unavailable

If you want a custom thank-you page for the fallback path, add _redirect.

<input type="hidden" name="_redirect" value="/thanks">

When JavaScript handles the request, you can show inline confirmation instead. When JavaScript does not run, Form Plume redirects after a successful POST.

File uploads

For attachments, add enctype="multipart/form-data" to the form and keep using FormData in fetch.

<form
  id="contact-form"
  action="PASTE_YOUR_FORM_PLUME_ENDPOINT_HERE"
  method="POST"
  enctype="multipart/form-data"
>
  <input type="file" name="attachment" accept=".pdf,.png,.jpg">
</form>

Form Plume stores uploads outside the email path, so large files do not break notification delivery.

JavaScript-specific checks

Use event.currentTarget or a form reference, not a loose collection of inputs, so future fields are included automatically.

Do not set Content-Type manually when sending FormData; the browser adds the multipart boundary.

Show both pending and failure states. A silent failed fetch is worse than a page redirect because the visitor cannot tell whether the form was received.

Keep status text close to the submit button with role="status" for success and role="alert" for errors.

Where Form Plume fits

The browser submits once. Form Plume receives the payload, validates it, filters spam, stores the submission, sends email notifications, and can fan accepted messages out to Slack, Discord, Google Sheets, Mailchimp, ActiveCampaign, or your own signed webhook.

Your JavaScript stays focused on the visitor experience instead of owning a backend pipeline.

Primary sources

FAQ

JavaScript form questions
before you ship.

Yes. JavaScript can post the browser form to a Form Plume endpoint. Form Plume sends email notifications, stores submissions, filters spam, accepts files, and triggers integrations without a custom server or email provider.

One line. Zero backend.

The form backend you don’t have to build.

Free foreverNo credit cardSet up in under a minute