formtoDashboard

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.