Next.js
Where the code goes
Put the generated React component in app/contact/contact-form.tsx, add 'use client' as its first line, and render it from app/contact/page.tsx. No API route is needed: the browser posts straight to formto.
The origin to allow
Allow your production hostname and your preview domains as allowed origins (for example example.com and your-app.vercel.app), and localhost for next dev.
Thank-you handling
The component shows its thank-you message in place. For a separate thank-you page, call router.push('/thanks') after a successful post.
File uploads
A file field in the definition uploads through the same FormData post; nothing changes in Next.js.
The code
Generated from the form’s fields (a React component). Replace YOUR_FORM_ID with your form’s public id, then restyle it; keep the field names and the hidden _gotcha honeypot.
import { useState, type FormEvent } from 'react'
const ACTION = "https://api.formto.io/f/YOUR_FORM_ID"
export default 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(ACTION, { method: 'POST', body: new FormData(form), headers: { Accept: 'application/json' } })
const body = (await res.json()) as { success?: boolean; error?: string }
if (!res.ok || !body.success) throw new Error(body.error || 'Something went wrong. Please try again.')
form.reset()
setStatus('sent')
} catch (err) {
setError(err instanceof Error ? err.message : 'Could not send. Please try again.')
setStatus('error')
}
}
return (
<form action={ACTION} method="post" encType="application/x-www-form-urlencoded" onSubmit={onSubmit}>
<h2>{"Contact"}</h2>
<p>
<label htmlFor="formto-name">{"Name"}</label>
<input type="text" id="formto-name" name="name" required />
</p>
<p>
<label htmlFor="formto-email">{"Email"}</label>
<input type="email" id="formto-email" name="email" autoComplete={"email"} required />
</p>
<p>
<label htmlFor="formto-message">{"Message"}</label>
<textarea id="formto-message" name="message" rows={5} required />
</p>
{/* Spam trap: people never see or fill this field. */}
<input type="text" name="_gotcha" tabIndex={-1} autoComplete="off" aria-hidden="true" style={{ position: 'absolute', left: '-10000px' }} />
<button type="submit" disabled={status === 'sending'}>Send</button>
<p role="status" aria-live="polite">{status === 'sent' ? 'Thanks, we got it.' : status === 'error' ? error : ''}</p>
</form>
)
}
Other platforms: Plain HTML, React, Astro, WordPress, Squarespace, Webflow, Wix, Framer, Shopify.