v0 Contact Form: Connect It to Formbold

v0 generates Next.js components with shadcn/ui, and its contact forms usually end in a mock Server Action. Swap that for your Formbold endpoint and the form starts sending.

Why doesn't my v0 contact form send?

v0 tends to finish a contact form with a placeholder. You'll often see a Server Action marked "use server" that waits a second and returns success, or a handler that only logs the data. Nothing is sent anywhere. Replace that with a request to your Formbold endpoint and every submission is stored and emailed to you.

What do I need before I start?

A Formbold form. Click Create New Form in All Forms, then copy the endpoint from Form Setup: https://formbold.com/s/YOUR_FORM_ID. The Free plan gives you 250 submissions a month and 10 forms.

What prompt do I give v0?

Pick v0.dev under Form Setup > Built with an AI Tool? for a prompt with your endpoint in it, or use this:

1
2
3
4
5
6
7
8
9
Replace the mock submit logic in the contact form.
Make the form a client component ("use client") and on submit
POST the fields to https://formbold.com/s/YOUR_FORM_ID with fetch,
a JSON body, and the headers Content-Type: application/json and
Accept: application/json.
Keep name attributes on every input: name, email, message.
Show the success state only when response.ok is true and an error
state otherwise. Remove the fake delay and any unused Server Action.
Keep the existing shadcn/ui design.

Can I keep a Server Action instead?

Yes, if you prefer. A Server Action can call the same endpoint with fetch, and the request is identical. The client version is simpler, though, and it keeps working if you later move the site to a static export. Pick one and delete the other, so there's no half-used action left behind.

What should the component look like?

Roughly this, inside the file v0 generated:

contact-form.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
"use client";

import { useState } from "react";

export function ContactForm() {
  const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");

  async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setStatus("sending");
    const form = e.currentTarget;
    const res = await fetch("https://formbold.com/s/YOUR_FORM_ID", {
      method: "POST",
      headers: { "Content-Type": "application/json", Accept: "application/json" },
      body: JSON.stringify(Object.fromEntries(new FormData(form))),
    });
    if (res.ok) form.reset();
    setStatus(res.ok ? "sent" : "error");
  }

  // keep v0's JSX, and make sure each <Input> and <Textarea> has a name
}

shadcn/ui's Input and Textarea pass name through to the real element, so <Input name="email" /> works as you'd expect.

How do I test it?

Submit once in v0's preview and check Submissions in Formbold. Then deploy to Vercel and test on the live URL. If nothing arrives, look for the request to formbold.com/s/... in the Network tab. If there isn't one, the old Server Action is probably still wired to the form.

What else should I switch on?

Basic Spam Filter in Settings > Spam & Security, right away. Because this form submits with fetch, use Custom reCAPTCHA if you want a captcha on top. And on Starter and higher you can send each entry to Slack, Notion or Google Sheets from the Integrations tab without touching the v0 code.

Last updated: October 19, 2026