The form
Replace YOUR-ACCESS-KEY with the key from your form's page. The highlighted line is the one that points the form at Formwire.
1"use client";2// app/contact/contact-form.tsx3import { useState } from "react";45export function ContactForm() {6const [status, setStatus] = useState<"idle" | "sending" | "sent" | "error">("idle");78async function onSubmit(e: React.FormEvent<HTMLFormElement>) {9e.preventDefault();10setStatus("sending");11const body = new FormData(e.currentTarget);12body.append("access_key", "YOUR-ACCESS-KEY");13const res = await fetch("https://formwire.io/submit", {14method: "POST",15headers: { Accept: "application/json" },16body,17});18const { success } = await res.json();19setStatus(success ? "sent" : "error");20}2122if (status === "sent") return <p>Thanks, your message is on its way.</p>;2324return (25<form onSubmit={onSubmit}>26<input type="text" name="fax" tabIndex={-1} autoComplete="off" className="hidden" />27<label htmlFor="name">Name</label>28<input id="name" name="name" type="text" required />29<label htmlFor="email">Email</label>30<input id="email" name="email" type="email" required />31<label htmlFor="message">Message</label>32<textarea id="message" name="message" rows={5} required />33<button type="submit" disabled={status === "sending"}>34{status === "sending" ? "Sending" : "Send"}35</button>36{status === "error" && <p>Something went wrong. Please try again.</p>}37</form>38);39}
Steps
- 1
Create a form and copy its access key
Sign up at app.formwire.com and create a form. Your sign-in address receives its submissions from the start. The access key is on the form's Settings tab, under General.
- 2
Add the client component
Create app/contact/contact-form.tsx with the code above and replace YOUR-ACCESS-KEY with your key. The "use client" directive is required because the component uses state and an event handler.
- 3
Render it from a page
Import the component into a server component page. The page itself stays a server component, so only the form ships JavaScript.
tsx 1
// app/contact/page.tsx2import { ContactForm } from "./contact-form";34export const metadata = { title: "Contact" };56export default function ContactPage() {7return (8<main>9<h1>Contact</h1>10<ContactForm />11</main>12);13} - 4
Send a test submission
Open localhost:3000/contact, fill the form in and send it. The submission appears in the form's inbox in the dashboard within a few seconds and the notification email follows, with Reply-To set to the address the visitor typed.
Notes for Next.js
- Pages Router: the same component works in pages/contact.tsx without the "use client" line, since every component there renders on the client.
- A server action is optional. Use one if you want to add fields or run code before the submission is stored, and post to Formwire from the action with fetch; the access key then never reaches the browser, though it is safe there too.
- The key can live in NEXT_PUBLIC_FORMWIRE_KEY and be read with process.env.NEXT_PUBLIC_FORMWIRE_KEY. It is a public ID either way, so inlining it is also fine.
- The Accept: application/json header makes Formwire answer with JSON instead of a redirect. Keep the redirect field out of the FormData; the component shows its own thank-you state.
Questions
Do I need an API route or a server action for the form?
No. The client component posts to Formwire directly. Add a server action only if you want your own code to run first, for example to add a field from your database or to check a login.
Is the access key safe to put in client-side code?
Yes. The access key is a public ID that can only deliver submissions to the inbox you verified. It cannot read submissions or change settings, and you can rotate it from the form's settings if it is ever abused.
How does the form stop spam without a captcha?
The hidden fax field is a honeypot: a submission that fills it in goes to the Spam folder and nobody is emailed. A spam filter and rate limits also run on every form. Settings, Spam & security has the filter level, and on Pro, allowed domains.
Where do submissions go?
Each submission is stored in the form's inbox in the dashboard and emailed to you with Reply-To set to the visitor. On Pro it can also go to more recipients and to webhooks. The inbox exports to CSV and JSON.