Frameworks and libraries
Add a contact form to Next.js
Next.js gives you a place to run server code, which is exactly why most Next.js contact forms end up as a half-finished /api/contact route with an SMTP client bolted on. WebForms replaces that route. Your form POSTs to an endpoint, we handle spam filtering, storage and fan-out to email, Slack or a webhook, and you keep your app free of mail credentials. Everything below works on the App Router, the Pages Router and a fully static `output: 'export'` build.
Setup
Create a form and copy your endpoint
Sign up, create a form and copy its endpoint URL. It looks like https://send.webforms.to/wft_your_key. There is no SDK to install and no package to add.
Point your form at it
Use the client component below for an inline success state, or the Server Action version if you want zero client JavaScript. Both send the same payload.
Choose where submissions land
In the dashboard, add destinations: an email address, a Slack channel, a Telegram chat, a webhook into your own API, or several at once. Every submission fans out to all of them.
Next.js examples
Swap in your own endpoint and these run as-is.
Client component with an inline success state. Requesting JSON keeps the browser on the page.
"use client";
import { useState } from "react";
const ENDPOINT = "https://send.webforms.to/wft_your_key";
export function ContactForm() {
const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");
async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus("sending");
const res = await fetch(ENDPOINT, {
method: "POST",
headers: { accept: "application/json" },
body: new FormData(e.currentTarget),
});
setStatus(res.ok ? "sent" : "error");
}
if (status === "sent") {
return <p role="status">Thanks - we'll be in touch.</p>;
}
return (
<form onSubmit={onSubmit}>
<input name="name" placeholder="Your name" required />
<input name="email" type="email" placeholder="you@example.com" required />
<textarea name="message" placeholder="Message" required />
{/* Honeypot: hidden from people, irresistible to bots. */}
<input
name="_gotcha"
tabIndex={-1}
autoComplete="off"
style={{ position: "absolute", left: "-9999px" }}
aria-hidden="true"
/>
<button disabled={status === "sending"}>
{status === "sending" ? "Sending…" : "Send"}
</button>
{status === "error" && <p role="alert">Something went wrong. Try again?</p>}
</form>
);
}Server Action variant. No client JavaScript at all - the form still works with JS disabled.
import { redirect } from "next/navigation";
export default function ContactPage() {
async function submit(formData: FormData) {
"use server";
const res = await fetch("https://send.webforms.to/wft_your_key", {
method: "POST",
headers: { accept: "application/json" },
body: formData,
});
if (!res.ok) throw new Error("Submission failed");
redirect("/contact/thanks");
}
return (
<form action={submit}>
<input name="name" required />
<input name="email" type="email" required />
<textarea name="message" required />
<button type="submit">Send</button>
</form>
);
}For output: 'export' there is no server at all. A plain form POST still works - we redirect the browser to _next when it finishes.
export default function ContactPage() {
return (
<form action="https://send.webforms.to/wft_your_key" method="POST">
<input name="name" required />
<input name="email" type="email" required />
<textarea name="message" required />
<input type="hidden" name="_next" value="https://example.com/thanks" />
<button type="submit">Send</button>
</form>
);
}Worth knowing
- Do not put the endpoint in a Server Action just to hide it. Submit keys are public by design - they identify the form, they do not authorise it. Lock a form down with domain restrictions in the dashboard instead.
- Skip useState entirely if you want: a native form POST with a _next field redirects the browser to your thank-you page and needs no JavaScript.
- Server Actions run on your server, so the submission's IP is your server's IP. Use the client component if you want accurate per-visitor spam signals.
Next.js form FAQ
No. The form posts straight to your WebForms endpoint. You can delete the /api/contact route and the SMTP dependency that usually comes with it.
Yes. A native form POST needs no server, so `output: 'export'` builds on GitHub Pages, S3 or any CDN work exactly the same. Add a _next field to control where the browser lands afterwards.
Yes. Forward the FormData from inside the action, as in the second example. Note that the request then comes from your server, so per-visitor IP spam signals are lost.
Yes. The submit key identifies which form a submission belongs to, it grants no access to your data. Restrict the form to your own domains in the dashboard to stop others posting to it.
Related guides
Ready to wire up your Next.js form?
Free for 300 submissions a month. Endpoint in under a minute, no card required.
Start free