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-formimport { 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>
);
}