FormsFort

React

FormsFort React guide for simple contact forms, React Hook Form, file uploads, and captcha tokens.

React

Submit React forms without a custom backend. FormsFort keeps the Web3Forms-style browser submission flow: send JSON for normal fields, send FormData for uploads, and render the API message returned by the public endpoint.

Simple React contact form

This mirrors the plain Web3Forms React pattern: prevent the browser navigation, build a payload from the form, post to the public endpoint, then reset only after a successful response.

import { useState } from "react";

export function ContactForm() {
  const [result, setResult] = useState("");

  async function onSubmit(event) {
    event.preventDefault();
    const form = event.currentTarget;
    const formData = new FormData(form);

    setResult("Sending...");

    const response = await fetch("https://api.formsfort.dev/submit", {
      method: "POST",
      headers: {
        accept: "application/json",
        "content-type": "application/json",
      },
      body: JSON.stringify({
        access_key: "YOUR_ACCESS_KEY",
        subject: "New website lead",
        from_name: "Example Site",
        name: formData.get("name"),
        email: formData.get("email"),
        message: formData.get("message"),
      }),
    });

    const body = await response.json();
    setResult(body.message || (response.ok ? "Submitted." : "Submission failed."));

    if (response.ok) {
      form.reset();
    }
  }

  return (
    <form onSubmit={onSubmit}>
      <input name="name" autoComplete="name" required />
      <input name="email" type="email" autoComplete="email" required />
      <textarea name="message" required />
      <input type="checkbox" name="botcheck" hidden />
      <button type="submit">Send</button>
      <p role="status">{result}</p>
    </form>
  );
}

React Hook Form

React Hook Form is a client-side convenience layer. FormsFort receives the same field names and reserved options as a plain HTML form.

import { useState } from "react";
import { useForm } from "react-hook-form";

export function ContactForm() {
  const [result, setResult] = useState("");
  const {
    register,
    handleSubmit,
    reset,
    formState: { errors, isSubmitting },
  } = useForm({ mode: "onTouched" });

  async function onSubmit(fields) {
    setResult("Sending...");

    const response = await fetch("https://api.formsfort.dev/submit", {
      method: "POST",
      headers: {
        accept: "application/json",
        "content-type": "application/json",
      },
      body: JSON.stringify({
        access_key: "YOUR_ACCESS_KEY",
        subject: "New website lead",
        from_name: "Example Site",
        ...fields,
      }),
    });

    const body = await response.json();
    setResult(body.message || (response.ok ? "Submitted." : "Submission failed."));

    if (response.ok) {
      reset();
    }
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        {...register("name", { required: "Name is required", maxLength: 80 })}
        autoComplete="name"
      />
      {errors.name && <p>{errors.name.message}</p>}

      <input
        type="email"
        {...register("email", { required: "Email is required" })}
        autoComplete="email"
      />
      {errors.email && <p>{errors.email.message}</p>}

      <textarea {...register("message", { required: "Message is required" })} />
      {errors.message && <p>{errors.message.message}</p>}

      <input type="checkbox" {...register("botcheck")} hidden />
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? "Sending..." : "Send"}
      </button>
      <p role="status">{result}</p>
    </form>
  );
}

File upload form

Use FormData directly for normal React forms or React Hook Form file fields. Do not set the content-type header; the browser adds the multipart boundary.

async function onSubmit(event) {
  event.preventDefault();
  const form = event.currentTarget;
  const formData = new FormData(form);

  formData.set("access_key", "YOUR_ACCESS_KEY");

  const response = await fetch("https://api.formsfort.dev/submit", {
    method: "POST",
    headers: { accept: "application/json" },
    body: formData,
  });

  return response.json();
}

export function UploadForm() {
  return (
    <form onSubmit={onSubmit}>
      <input name="name" required />
      <input name="email" type="email" required />
      <input name="attachment" type="file" required />
      <button type="submit">Upload</button>
    </form>
  );
}

React Hook Form file upload

React Hook Form returns file inputs as a FileList. Append the first file manually before sending multipart FormData.

async function onSubmit(fields) {
  const formData = new FormData();
  formData.append("access_key", "YOUR_ACCESS_KEY");
  formData.append("name", fields.name);
  formData.append("email", fields.email);
  formData.append("attachment", fields.attachment[0]);

  const response = await fetch("https://api.formsfort.dev/submit", {
    method: "POST",
    headers: { accept: "application/json" },
    body: formData,
  });

  return response.json();
}

Captcha token fields

Load hCaptcha, Google reCAPTCHA v3, or Cloudflare Turnstile in the browser. Add the generated token under the matching Web3Forms-compatible field name before submitting.

// Google reCAPTCHA v3
formData.set("recaptcha_response", recaptchaToken);

// hCaptcha
formData.set("h-captcha-response", hcaptchaToken);

// Cloudflare Turnstile
formData.set("cf-turnstile-response", turnstileToken);

React package

Install the FormsFort hook when you want the Web3Forms-style React helper API instead of writing the fetch wrapper yourself. Keep the endpoint configurable until your production API domain is finalized.

pnpm add @formsfort/react react-hook-form
import { useForm } from "react-hook-form";
import useFormsFort from "@formsfort/react";

export default function ContactForm() {
  const { register, handleSubmit, reset } = useForm();
  const { submit, loading, result } = useFormsFort({
    access_key: "YOUR_ACCESS_KEY",
    endpoint: "https://api.formsfort.dev/submit",
    settings: {
      from_name: "Example Site",
      subject: "New website lead",
    },
    onSuccess: () => reset(),
  });

  return (
    <form onSubmit={handleSubmit(submit)}>
      <input type="text" {...register("name", { required: true })} />
      <input type="email" {...register("email", { required: true })} />
      <textarea {...register("message", { required: true })} />
      <input type="checkbox" {...register("botcheck")} hidden />
      <button disabled={loading}>Submit</button>
      {result?.message ? <p>{result.message}</p> : null}
    </form>
  );
}

On this page