React
Add a form backend to your React application in minutes. Forminit handles submissions, validation, file uploads, and notifications.
Overview
Section titled “Overview”This guide covers two integration approaches:
| Approach | Use Case | Authentication |
|---|---|---|
| Client-side | Public forms, static sites, SPAs | Form set to “Public” mode |
| With Backend Proxy | Protected forms, server-side validation | API key on your server |
Choose client-side for simple contact forms on public websites. Choose backend proxy when you need API key protection or server-side processing.
Client-Side Integration
Section titled “Client-Side Integration”Best for public forms where no API key is required.
Prerequisites
Section titled “Prerequisites”- Create a Forminit account at forminit.com
- Create a form in your dashboard
- Set authentication mode to Public in Form Settings
Step 1: Install the SDK
Section titled “Step 1: Install the SDK”# npm
npm install forminit
# yarn
yarn add forminit
# pnpm
pnpm add forminit
Step 2: Create a Form Component
Section titled “Step 2: Create a Form Component”import { useState, FormEvent } from 'react';
import { Forminit } from 'forminit';
const forminit = new Forminit();
const FORM_ID = 'YOUR_FORM_ID';
export function ContactForm() {
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
const [error, setError] = useState<string | null>(null);
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus('loading');
setError(null);
const form = e.currentTarget;
const formData = new FormData(form);
const { data, redirectUrl, error } = await forminit.submit(FORM_ID, formData);
if (error) {
setStatus('error');
setError(error.message);
return;
}
setStatus('success');
form.reset();
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
name="fi-sender-firstName"
placeholder="First name"
required
/>
<input
type="text"
name="fi-sender-lastName"
placeholder="Last name"
required
/>
<input
type="email"
name="fi-sender-email"
placeholder="Email"
required
/>
<textarea
name="fi-text-message"
placeholder="Message"
required
/>
{status === 'error' && <p className="error">{error}</p>}
{status === 'success' && <p className="success">Message sent successfully!</p>}
<button type="submit" disabled={status === 'loading'}>
{status === 'loading' ? 'Sending...' : 'Send'}
</button>
</form>
);
}
Backend Proxy Integration
Section titled “Backend Proxy Integration”Best for protected forms that require API key authentication. This approach keeps your API key secure on your server.
Prerequisites
Section titled “Prerequisites”- Create a Forminit account at forminit.com
- Create a form in your dashboard
- Set authentication mode to Protected in Form Settings
- Create an API token from Account → API Tokens
Step 1: Install the SDK
Section titled “Step 1: Install the SDK”# npm
npm install forminit
# yarn
yarn add forminit
# pnpm
pnpm add forminit
Step 2: Set Up Your Backend Proxy
Section titled “Step 2: Set Up Your Backend Proxy”You’ll need a backend server to securely proxy requests to Forminit. Here’s an example using Express:
// server.js
import express from 'express';
import cors from 'cors';
const app = express();
app.use(cors());
app.use(express.json());
const FORMINIT_API_KEY = process.env.FORMINIT_API_KEY;
app.post('/api/forminit/:formId', async (req, res) => {
const { formId } = req.params;
const response = await fetch(`https://forminit.com/f/${formId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-KEY': FORMINIT_API_KEY,
},
body: JSON.stringify(req.body),
});
const data = await response.json();
res.status(response.status).json(data);
});
app.listen(3001, () => {
console.log('Proxy server running on port 3001');
});
Step 3: Create a Form Component
Section titled “Step 3: Create a Form Component”import { useState, FormEvent } from 'react';
import { Forminit } from 'forminit';
const forminit = new Forminit({
proxyUrl: 'http://localhost:3001/api/forminit',
});
const FORM_ID = 'YOUR_FORM_ID';
export function ContactForm() {
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
const [error, setError] = useState<string | null>(null);
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus('loading');
setError(null);
const form = e.currentTarget;
const formData = new FormData(form);
const { data, redirectUrl, error } = await forminit.submit(FORM_ID, formData);
if (error) {
setStatus('error');
setError(error.message);
return;
}
setStatus('success');
form.reset();
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
name="fi-sender-firstName"
placeholder="First name"
required
/>
<input
type="text"
name="fi-sender-lastName"
placeholder="Last name"
required
/>
<input
type="email"
name="fi-sender-email"
placeholder="Email"
required
/>
<textarea
name="fi-text-message"
placeholder="Message"
required
/>
{status === 'error' && <p className="error">{error}</p>}
{status === 'success' && <p className="success">Message sent!</p>}
<button type="submit" disabled={status === 'loading'}>
{status === 'loading' ? 'Sending...' : 'Send'}
</button>
</form>
);
}
JSON Submission
Section titled “JSON Submission”For more control over the request payload, use JSON format instead of FormData:
import { useState } from 'react';
import { Forminit } from 'forminit';
const forminit = new Forminit();
const FORM_ID = 'YOUR_FORM_ID';
export function ContactForm() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
const [error, setError] = useState<string | null>(null);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setStatus('loading');
setError(null);
const { data, redirectUrl, error } = await forminit.submit(FORM_ID, {
blocks: [
{
type: 'sender',
properties: {
email,
firstName,
lastName,
},
},
{
type: 'text',
name: 'message',
value: message,
},
],
});
if (error) {
setStatus('error');
setError(error.message);
return;
}
setStatus('success');
setFirstName('');
setLastName('');
setEmail('');
setMessage('');
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
placeholder="First name"
required
/>
<input
type="text"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
placeholder="Last name"
required
/>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
/>
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Message"
required
/>
{status === 'error' && <p className="error">{error}</p>}
{status === 'success' && <p className="success">Message sent!</p>}
<button type="submit" disabled={status === 'loading'}>
{status === 'loading' ? 'Sending...' : 'Send'}
</button>
</form>
);
}
Note: JSON submissions do not support file uploads. Use FormData for forms with file fields.
File Uploads
Section titled “File Uploads”File uploads require FormData. Add file inputs with the fi-file-{name} naming pattern:
import { useState, useRef, FormEvent } from 'react';
import { Forminit } from 'forminit';
const forminit = new Forminit();
const FORM_ID = 'YOUR_FORM_ID';
export function ApplicationForm() {
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
const [error, setError] = useState<string | null>(null);
const formRef = useRef<HTMLFormElement>(null);
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus('loading');
setError(null);
const formData = new FormData(e.currentTarget);
const { data, error } = await forminit.submit(FORM_ID, formData);
if (error) {
setStatus('error');
setError(error.message);
return;
}
setStatus('success');
formRef.current?.reset();
}
return (
<form ref={formRef} onSubmit={handleSubmit}>
<input
type="text"
name="fi-sender-firstName"
placeholder="First name"
required
/>
<input
type="text"
name="fi-sender-lastName"
placeholder="Last name"
required
/>
<input
type="email"
name="fi-sender-email"
placeholder="Email"
required
/>
<label>Resume (PDF)</label>
<input
type="file"
name="fi-file-resume"
accept=".pdf,.doc,.docx"
required
/>
<label>Portfolio (optional, multiple files)</label>
<input
type="file"
name="fi-file-portfolio[]"
accept="image/*,.pdf"
multiple
/>
{status === 'error' && <p className="error">{error}</p>}
{status === 'success' && <p className="success">Application submitted!</p>}
<button type="submit" disabled={status === 'loading'}>
{status === 'loading' ? 'Submitting...' : 'Submit Application'}
</button>
</form>
);
}
Important: When using the
multipleattribute, append[]to the field name (e.g.,fi-file-portfolio[]).
Response Structure
Section titled “Response Structure”The SDK returns { data, redirectUrl, error }. On successful submission:
data contains the submission details:
{
"hashId": "7LMIBoYY74JOCp1k",
"date": "2026-01-01 21:10:24",
"blocks": {
"sender": {
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com"
},
"message": "Hello world"
}
}
redirectUrl contains the thank you page URL (always returned).
| Field | Type | Description |
|---|---|---|
data.hashId | string | Unique submission identifier |
data.date | string | Submission timestamp (YYYY-MM-DD HH:mm:ss) |
data.blocks | object | All submitted field values |
redirectUrl | string | Thank you page URL |
Error Response:
{
"error": "ERROR_CODE",
"code": 400,
"message": "Human-readable error message"
}
Available Form Blocks
Section titled “Available Form Blocks”For complete documentation on all available blocks, field naming conventions, and validation rules, see the Form Blocks Reference.
Error Handling
Section titled “Error Handling”Handle common errors appropriately:
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus('loading');
setError(null);
const formData = new FormData(e.currentTarget);
const { data, error } = await forminit.submit(FORM_ID, formData);
if (error) {
setStatus('error');
switch (error.error) {
case 'FI_SCHEMA_FORMAT_EMAIL':
setError('Please enter a valid email address.');
break;
case 'FI_RULES_PHONE_INVALID':
setError('Please enter a valid phone number (e.g., +12025550123).');
break;
case 'FI_SCHEMA_RANGE_RATING':
setError('Rating must be between 1 and 5.');
break;
case 'TOO_MANY_REQUESTS':
setError('Please wait a moment before submitting again.');
break;
default:
setError(error.message);
}
return;
}
setStatus('success');
e.currentTarget.reset();
}
Common Error Codes
Section titled “Common Error Codes”| Error Code | Description |
|---|---|
FORM_NOT_FOUND | Form ID doesn’t exist or was deleted |
FORM_DISABLED | Form is disabled by owner |
EMPTY_SUBMISSION | No fields with values submitted |
FI_SCHEMA_FORMAT_EMAIL | Invalid email format |
FI_RULES_PHONE_INVALID | Invalid phone number format |
FI_SCHEMA_RANGE_RATING | Rating not between 1-5 |
FI_DATA_COUNTRY_INVALID | Invalid country code |
TOO_MANY_REQUESTS | Rate limit exceeded |
Security Best Practices
Section titled “Security Best Practices”- Use backend proxy for protected forms — Never expose API keys in client-side code
- Validate input client-side — Provide immediate feedback to users
- Don’t rely solely on client validation — Forminit validates server-side automatically
- Use HTTPS — Always use secure connections in production
Related Documentation
Section titled “Related Documentation”- Form Blocks Reference — Complete reference for all block types
- File Uploads — Detailed file upload guide
- API Reference — Full REST API documentation
- Next.js Integration — Next.js specific setup
- Node.js Integration — Server-side Node.js setup
- reCAPTCHA Integration — Add reCAPTCHA protection
- hCaptcha Integration — Add hCaptcha protection
- Honeypot — Add honeypot spam protection