Form Integration

Submit a form with Ajax and keep visitors on the page

A full page reload after Send feels dated, and you don't need jQuery to avoid it. The script below catches the submit, posts to your Formbold endpoint with fetch and writes a status line under the button. You still get every entry by email.

Ajax-powered Contact Form

How do I submit a form with Ajax?

Catch the submit event, send the fields with fetch, then show the result. This snippet does all three in about 40 lines of plain JavaScript, no jQuery. Replace FORM_ID with your own.

ajax.html
<form id="contact-form" class="flex flex-col gap-4 max-w-[452px] mx-auto">
  <!-- Email Address -->
  <div class="flex flex-col gap-2">
    <label for="email" class="text-sm font-medium text-gray-700">
      Email Address<span class="ml-0.5 text-red-500">*</span>
    </label>
    <input
      type="email"
      name="email"
      id="email"
      placeholder="Enter your email"
      required
      class="max-h-11 w-full rounded-xl border border-gray-300 bg-white px-4 py-2.5 text-base text-gray-900 transition-all placeholder:text-[#9ca3af] focus:border-[#5750f1] focus:ring-4 focus:ring-[#5750f1]/10 focus:outline-none"
    />
  </div>

  <!-- Subject -->
  <div class="flex flex-col gap-2">
    <label for="subject" class="text-sm font-medium text-gray-700">
      Subject<span class="ml-0.5 text-red-500">*</span>
    </label>
    <input
      type="text"
      name="subject"
      id="subject"
      placeholder="Enter your subject"
      required
      class="max-h-11 w-full rounded-xl border border-gray-300 bg-white px-4 py-2.5 text-base text-gray-900 transition-all placeholder:text-[#9ca3af] focus:border-[#5750f1] focus:ring-4 focus:ring-[#5750f1]/10 focus:outline-none"
    />
  </div>

  <!-- Message -->
  <div class="flex flex-col gap-2">
    <label for="message" class="text-sm font-medium text-gray-700">
      Message<span class="ml-0.5 text-red-500">*</span>
    </label>
    <textarea
      name="message"
      id="message"
      rows="5"
      placeholder="Type your message..."
      required
      class="w-full resize-y rounded-xl border border-gray-300 bg-white px-4 py-3 text-base text-gray-900 transition-all placeholder:text-[#9ca3af] focus:border-[#5750f1] focus:ring-4 focus:ring-[#5750f1]/10 focus:outline-none"
    ></textarea>
  </div>

  <!-- Submit Button -->
  <button
    type="submit"
    id="submit-btn"
    class="flex h-12 w-full cursor-pointer items-center justify-center gap-2 rounded-xl bg-[#111827] px-5 py-3 text-base font-medium text-white shadow-xs transition-all hover:bg-black active:scale-[0.99] disabled:opacity-50"
  >
    Submit Message
  </button>

  <p id="form-status" class="hidden rounded-xl p-3 text-center text-sm font-medium"></p>
</form>

<script>
  const form = document.getElementById("contact-form");
  const submitBtn = document.getElementById("submit-btn");
  const statusEl = document.getElementById("form-status");

  form.addEventListener("submit", async function (e) {
    e.preventDefault();
    submitBtn.disabled = true;
    submitBtn.textContent = "Submitting...";

    const formData = new FormData(form);
    const json = JSON.stringify(Object.fromEntries(formData));

    try {
      const response = await fetch("https://formbold.com/s/FORM_ID", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Accept: "application/json",
        },
        body: json,
      });

      const result = await response.json();
      statusEl.classList.remove("hidden");
      if (response.ok) {
        statusEl.className = "rounded-xl p-3 text-center text-sm font-medium bg-emerald-50 text-emerald-700";
        statusEl.textContent = result.message || "Message sent successfully!";
        form.reset();
      } else {
        statusEl.className = "rounded-xl p-3 text-center text-sm font-medium bg-red-50 text-red-700";
        statusEl.textContent = result.message || "Something went wrong!";
      }
    } catch (error) {
      statusEl.classList.remove("hidden");
      statusEl.className = "rounded-xl p-3 text-center text-sm font-medium bg-red-50 text-red-700";
      statusEl.textContent = "Error submitting form.";
    } finally {
      submitBtn.disabled = false;
      submitBtn.textContent = "Submit Message";
      setTimeout(() => statusEl.classList.add("hidden"), 4000);
    }
  });
</script>
How it works

What are the setup steps?

The only fiddly part is the Accept header. Leave it in.

1

Make a form in the dashboard

Click Create New Form and give it a name you'll recognise later. Free accounts can have 10.

2

Paste the endpoint

Put https://formbold.com/s/FORM_ID from Form Setup in the fetch call. Keep the Accept: application/json header so the script can read the response.

3

Test it

Submit once with the Network tab open. A 200 response means it worked, and the entry is already in Submissions.

Features

After the request lands

It stores the entry, runs your spam settings, sends the email and passes the data to any connected apps, then replies to your script.

No backend script

No PHP handler or Node server behind the form. Your page and one fetch call are the whole stack.

Custom reCAPTCHA for fetch

Automatic reCAPTCHA is built for plain HTML posts. For an Ajax form, use Custom reCAPTCHA with your own v2 keys, plus Basic Spam Filter.

File uploads

Send a FormData body without a Content-Type header and files come through. Starter and higher.

Hand entries to other tools

From Starter, Formbold can forward each entry to Discord, Zapier, Make or your own webhook, so your script never has to.

FAQs

Ajax form questions

The usual stumbling blocks, and how to get past them.

No. fetch is built into every current browser. If your site already loads jQuery, $.ajax with the same URL and headers works too.

Still have a question?

The docs cover setup, framework guides, integrations and spam protection, step by step.