Next.js forms
Formkove works with Next.js without an API route or a server action. The browser posts straight to your form’s endpoint.
Before you start: add your site’s origin (e.g.
https://example.com, andhttp://localhost:3000for local development) under your form’s Settings → Allowed Origins & Redirects. Browsers blockfetch()responses from sites that aren’t listed. See Allowed Origins.
Client component (App Router)
Section titled “Client component (App Router)”"use client";
import { useState, type FormEvent } from "react";
const ENDPOINT = `https://app.formkove.com/api/forms/${process.env.NEXT_PUBLIC_FORMKOVE_FORM_ID}/submissions`;
export function ContactForm() { const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle"); const [error, setError] = useState("");
async function onSubmit(event: FormEvent<HTMLFormElement>) { event.preventDefault(); const form = event.currentTarget; setStatus("sending");
try { const res = await fetch(ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json" }, body: JSON.stringify(Object.fromEntries(new FormData(form))), }); const json = await res.json(); if (!res.ok) throw new Error(json.error || "Something went wrong."); form.reset(); setStatus("sent"); } catch (err) { setError(err instanceof Error ? err.message : "Network error. Please try again."); setStatus("error"); } }
if (status === "sent") return <p>Thanks! We'll be in touch.</p>;
return ( <form onSubmit={onSubmit}> <input name="name" placeholder="Your name" required /> <input name="email" type="email" placeholder="Email address" required /> <textarea name="message" placeholder="Message" required /> <button type="submit" disabled={status === "sending"}> {status === "sending" ? "Sending..." : "Send"} </button> {status === "error" && <p role="alert">{error}</p>} </form> );}Use it from any page:
import { ContactForm } from "./contact-form";
export default function ContactPage() { return <ContactForm />;}Set the form ID in .env.local:
NEXT_PUBLIC_FORMKOVE_FORM_ID=form1_your_form_idThe form ID is safe to expose. It can only send submissions to its own form.
Pages Router
Section titled “Pages Router”The same component works in pages/contact.tsx. Remove the "use client" line.
Without JavaScript
Section titled “Without JavaScript”A plain HTML form also works in Next.js and needs no Allowed Origins entry for the submission itself:
<form action={`https://app.formkove.com/api/forms/${process.env.NEXT_PUBLIC_FORMKOVE_FORM_ID}/submissions?redirect=https://example.com/thanks`} method="POST"> {/* fields */}</form>The redirect target must be listed under Allowed Origins. See Success pages and redirects.
Why not a server action?
Section titled “Why not a server action?”You can post from a server action or route handler, but every submission then comes from your server’s IP address. Formkove rate limits per IP and form, so all your visitors would share one limit. Submitting from the browser keeps the limit per visitor.