Forms reference — Findit Next.js
The site’s four forms — the footer newsletter, Contact, “Schedule a tour” on property pages and “Add property” — share one pipeline:
<Form> (design system) → server action (src/lib/actions.ts) → deliver() → provider(s) chosen in FORM_PROVIDERThe forms, their states (sending, “Thank you”, “Something went wrong”) and the spam honeypot work out of the box. Where the submissions go is a setting, not code.
1. Pick a provider
Section titled “1. Pick a provider”Copy .env.example to .env.local (or add the variables in your host, e.g. Vercel → Settings → Environment Variables) and set FORM_PROVIDER.
FORM_PROVIDER |
What happens | Variables |
|---|---|---|
console (default) |
Printed to the server log. For development. | — |
webhook |
Posted as JSON to a URL — Zapier, Make, n8n, Pipedream, Formspree, Slack workflow, your own API. | FORM_WEBHOOK_URL, optional FORM_WEBHOOK_SECRET (sent as Authorization: Bearer …) |
mailchimp |
Sender added or updated in an audience, tagged with the form, the rest saved as a note. | MAILCHIMP_API_KEY, MAILCHIMP_AUDIENCE_ID, optional MAILCHIMP_STATUS |
resend |
Emailed to you; replying answers the sender. | RESEND_API_KEY, FORM_EMAIL_TO, FORM_EMAIL_FROM |
Use several at once with a comma: FORM_PROVIDER=resend,mailchimp emails you and adds the contact to Mailchimp.
If any of them fails, the visitor sees “Something went wrong” (their input stays in the form) and the reason is logged on the server.
Mailchimp notes
Section titled “Mailchimp notes”- API key: Profile → Extras → API keys. It ends in your data center (
…-us21); nothing else to configure. - Audience ID: Audience → Settings → Audience name and defaults.
- Consent: new contacts are added as
transactional— they can receive replies, not campaigns. Usependingto send a double opt-in email, orsubscribedonly if the form asks for marketing consent. - Merge fields: name →
FNAME(the whole name), phone →PHONE(a new audience’s defaults). Change the map at the top ofproviders/mailchimp.tsif your audience uses other tags.
Webhook payload
Section titled “Webhook payload”{ "form": "contact", "title": "New contact enquiry", "submittedAt": "2026-09-16T10:48:23.256Z", "data": { "name": "Ana Lee", "email": "ana@example.com", "phone": "+1 555 0100", "message": "Hi!" }, "labels": { "name": "Name", "email": "Email", "phone": "Phone", "message": "Message" }}form is newsletter, contact, tour or property. The tour form’s data also carries propertyId and propertyName; “Add property” carries the fields of that form; the newsletter only email.
2. Add your own provider (HubSpot, Brevo, Airtable, a database…)
Section titled “2. Add your own provider (HubSpot, Brevo, Airtable, a database…)”-
Create
src/lib/forms/providers/<name>.ts:import { env, toLines, valueOf, type FormProvider } from "../types";export const brevoProvider: FormProvider = {name: "brevo",async send(submission) {const response = await fetch("https://api.brevo.com/v3/contacts", {method: "POST",headers: { "api-key": env("BREVO_API_KEY"), "Content-Type": "application/json" },body: JSON.stringify({email: valueOf(submission, "email"),attributes: { FIRSTNAME: valueOf(submission, "name") },updateEnabled: true,}),});if (!response.ok) throw new Error(`Brevo responded ${response.status}: ${await response.text()}`);},}; -
Register it in
providers/index.ts(brevo: brevoProvider). -
Set
FORM_PROVIDER=brevoand its variables in.env.example/ your host.
Helpers from types.ts: valueOf(submission, name) returns one field as text, toLines(submission) returns
"Label: value" lines for every filled field, and env(name) reads a required variable with a clear error.
3. Change a form
Section titled “3. Change a form”- Fields and labels live in the page copy:
src/content/pages/contact.ts,property.ts(the tour form) andadd-property.ts; the newsletter’s texts are insrc/content/site.ts(nameis the key providers receive,labelis what people read). - Required fields are listed in
src/lib/actions.ts. Keep them in step with therequiredflags in the copy, which the browser checks first. - A new form: lay it out with
<Form>,<FormField>and<RadioGroup>, add a server action next to the others that callsdeliver(data, { form, title, fields, required }), and add the form name toFormKindintypes.ts.