close
Skip to content

Add forms to React

Add a form backend to your React application in minutes. Forminit handles submissions, validation, file uploads, and notifications.

Build with AI

Use our LLM-ready skills to integrate Forminit with your form for faster integration. Edit the example prompt with your needs and paste your Form ID. Your AI tool will handle the rest.

EXAMPLE PROMPT

Build me a contact form with name, email, and message fields. Connect it with Forminit for form submissions.

formId: <PASTE-YOUR-FORM-ID-HERE>

Use this integration skill guide: https://forminit.com/skills/forminit-react/SKILL.md


This guide covers two integration approaches:

ApproachUse CaseAuthentication
Client-sidePublic forms, static sites, SPAsForm set to “Public” mode
With Backend ProxyProtected forms, server-side validationAPI 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.


Best for public forms where no API key is required.

  1. Create a Forminit account at forminit.com
  2. Create a form in your dashboard
  3. Set authentication mode to Public in Form Settings
# npm
npm install forminit

# yarn
yarn add forminit

# pnpm
pnpm add forminit
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>
  );
}

Best for protected forms that require API key authentication. This approach keeps your API key secure on your server.

  1. Create a Forminit account at forminit.com
  2. Create a form in your dashboard
  3. Set authentication mode to Protected in Form Settings
  4. Create an API token from Account → API Tokens
# npm
npm install forminit

# yarn
yarn add forminit

# pnpm
pnpm add forminit

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');
});
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>
  );
}

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 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 multiple attribute, append [] to the field name (e.g., fi-file-portfolio[]).


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).

FieldTypeDescription
data.hashIdstringUnique submission identifier
data.datestringSubmission timestamp (YYYY-MM-DD HH:mm:ss)
data.blocksobjectAll submitted field values
redirectUrlstringThank you page URL

Error Response:

{
  "error": "ERROR_CODE",
  "code": 400,
  "message": "Human-readable error message"
}

For complete documentation on all available blocks, field naming conventions, and validation rules, see the Form Blocks Reference.


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();
}
Error CodeDescription
FORM_NOT_FOUNDForm ID doesn’t exist or was deleted
FORM_DISABLEDForm is disabled by owner
EMPTY_SUBMISSIONNo fields with values submitted
FI_SCHEMA_FORMAT_EMAILInvalid email format
FI_RULES_PHONE_INVALIDInvalid phone number format
FI_SCHEMA_RANGE_RATINGRating not between 1-5
FI_DATA_COUNTRY_INVALIDInvalid country code
TOO_MANY_REQUESTSRate limit exceeded

  1. Use backend proxy for protected forms — Never expose API keys in client-side code
  2. Validate input client-side — Provide immediate feedback to users
  3. Don’t rely solely on client validation — Forminit validates server-side automatically
  4. Use HTTPS — Always use secure connections in production