formtoDashboard

React

Where the code goes

Save the generated component as ContactForm.tsx (it imports nothing but React) and render <ContactForm /> where the form belongs. Generate it with formto_generate_form_code (framework react) or from the dashboard.

The origin to allow

Allow your app's hostname as an allowed origin, plus localhost while you develop.

Thank-you handling

The component keeps the thank-you state itself: after a successful post it clears the form and shows the message. Replace the message with your own component if you like.

File uploads

File inputs work as is: the component posts FormData, so a file field in the definition uploads with the response.

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, Next.js, Astro, WordPress, Squarespace, Webflow, Wix, Framer, Shopify.