Skip to content

Add forms to Next.js

Forminit+Next.js

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

  1. Sign up or log in at forminit.com
  2. Create a new form
  3. Go to Form Settings → Set authentication mode to Public
  4. Copy your Form ID (e.g., YOUR-FORM-ID)

npm install forminit

The example below packs every block type into a single component so you can see the full naming pattern in one place. Every input name follows fi-{blockType}-{name} — that’s the only rule. Most forms only need a handful of these, so keep the blocks you need and delete the rest.

// components/ContactForm.tsx

'use client';

import { useState, useRef } from 'react';
import { Forminit } from 'forminit';

const forminit = new Forminit();

export function ContactForm() {
  const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
  const [errorMessage, setErrorMessage] = useState<string | null>(null);
  const formRef = useRef<HTMLFormElement>(null);

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setStatus('loading');
    setErrorMessage(null);

    const formData = new FormData(e.currentTarget);
    const { data, error } = await forminit.submit('YOUR_FORM_ID', formData);

    if (error) {
      setStatus('error');
      setErrorMessage(error.message);
      return;
    }

    setStatus('success');
    formRef.current?.reset();
  }

  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      {/* sender: who's submitting (built-in, max 1 per submission) */}
      <input type="text"  name="fi-sender-firstName" placeholder="First name" />
      <input type="text"  name="fi-sender-lastName"  placeholder="Last name" />
      {/* Single-field alternative to firstName/lastName */}
      <input type="text"  name="fi-sender-fullName"  placeholder="Full name" />
      <input type="email" name="fi-sender-email"     placeholder="Email" required />
      <input type="text"  name="fi-sender-phone"     placeholder="Phone" />
      <input type="text"  name="fi-sender-title"     placeholder="Title (Mr., Ms., Dr.)" />
      <input type="text"  name="fi-sender-company"   placeholder="Company" />
      <input type="text"  name="fi-sender-position"  placeholder="Job title" />
      <input type="text"  name="fi-sender-address"   placeholder="Address" />
      <input type="text"  name="fi-sender-address2"  placeholder="Address line 2 (apt, suite, etc.)" />
      <input type="text"  name="fi-sender-city"      placeholder="City" />
      <input type="text"  name="fi-sender-postcode"  placeholder="Postcode / ZIP" />
      <input type="text"  name="fi-sender-country"   placeholder="Country (ISO 3166-1 alpha-2, e.g. US)" />
      <input type="hidden" name="fi-sender-userId"   value="ext_12345" />

      {/* text: free-form answer, no validation */}
      <textarea name="fi-text-message" placeholder="Message" />

      {/* number: any numeric value */}
      <input type="number" name="fi-number-budget" placeholder="Budget" />

      {/* email: a second email beyond the sender's */}
      <input type="email" name="fi-email-invitee" placeholder="Invitee email" />

      {/* phone: a second phone beyond the sender's, E.164 format */}
      <input type="text" name="fi-phone-emergency" placeholder="Emergency contact" />

      {/* url: validated as a URL */}
      <input type="url" name="fi-url-website" placeholder="Your website" />

      {/* select: single or multi choice */}
      <select name="fi-select-plan">
        <option value="basic">Basic</option>
        <option value="pro">Pro</option>
        <option value="enterprise">Enterprise</option>
      </select>

      {/* radio: single choice from a group */}
      <label><input type="radio" name="fi-radio-priority" value="low"  /> Low</label>
      <label><input type="radio" name="fi-radio-priority" value="high" /> High</label>

      {/* checkbox: one or more choices, same name = array */}
      <label><input type="checkbox" name="fi-checkbox-features" value="api" />     API</label>
      <label><input type="checkbox" name="fi-checkbox-features" value="support" /> Support</label>

      {/* rating: integer between 1 and 5 */}
      <input type="range" name="fi-rating-satisfaction" min={1} max={5} step={1} defaultValue={3} />

      {/* date: ISO 8601 date or date-time */}
      <input type="date" name="fi-date-appointment" />

      {/* file: requires multipart/form-data (FormData uses it by default) */}
      <input type="file" name="fi-file-resume" />

      {/* country: ISO 3166-1 alpha-2 code */}
      <select name="fi-country-shipping">
        <option value="US">United States</option>
        <option value="GB">United Kingdom</option>
      </select>

      {status === 'error' && (
        <p className="status-error">{errorMessage}</p>
      )}
      {status === 'success' && (
        <p className="status-success">Message sent successfully!</p>
      )}

      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Sending...' : 'Send Message'}
      </button>
    </form>
  );
}

For a full reference on each block type and its validation rules, see the Form Blocks documentation.


// app/contact/page.tsx

import { ContactForm } from '@/components/ContactForm';

export default function ContactPage() {
  return (
    <div className="contact-page">
      <h1>Contact Us</h1>
      <p>We'd love to hear from you. Fill out the form below and we'll get back to you soon.</p>
      
      <ContactForm />
    </div>
  );
}

For a complete list of available form blocks (text, email, phone, file, rating, select, etc.) and field naming patterns, see the Form Blocks documentation.


The SDK returns { data, redirectUrl, error }:

On success:

{
  data: {
    hashId: "7LMIBoYY74JOCp1k",      // Unique submission ID
    date: "2026-01-01 21:10:24",      // Timestamp
    blocks: {                          // Submitted values
      sender: {
        firstName: "John",
        lastName: "Doe",
        email: "john@example.com"
      },
      message: "Hello from Next.js!"
    }
  },
  redirectUrl: "https://forminit.com/thank-you"
}

On error:

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

Every successful submission returns a redirectUrl. You configure this URL in Form Settings → Redirections — so you can point users to a different thank-you page anytime without changing your code or redeploying. Just read redirectUrl from the response and send the user there:

async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
  e.preventDefault();

  const formData = new FormData(e.currentTarget);
  const { data, redirectUrl, error } = await forminit.submit('YOUR_FORM_ID', formData);

  if (error) {
    setStatus('error');
    setErrorMessage(error.message);
    return;
  }

  // Redirect to the URL configured in your Forminit form settings
  window.location.href = redirectUrl;
}

If the redirect target is a route inside your own Next.js app, use the router for a client-side navigation instead:

import { useRouter } from 'next/navigation';

const router = useRouter();

// ...after a successful submit:
router.push(redirectUrl);

The Forminit SDK includes TypeScript definitions:

import type { ForminitResponse, ForminitError } from 'forminit';

const { data, redirectUrl, error }: ForminitResponse = await forminit.submit(
  formId,
  formData
);

if (error) {
  const typedError: ForminitError = error;
  console.error(typedError.code, typedError.message);
}

  1. View your submissions in the Forminit dashboard
  2. Set up email notifications for new submissions
  3. Explore webhook integrations for advanced workflows