---
title: "Add forms to Next.js"
description: "Add a form backend to your Next.js app. Submit directly from your client components with validation, file uploads, and notifications."
url: https://forminit.com/docs/nextjs/
---

# Add forms to Next.js

![Forminit+Next.js](https://forminit.com/guides/next-thumbnail.png) 

> **Build with AI:** give your AI coding tool your Form ID and this integration skill guide: https://forminit.com/skills/forminit-react/SKILL.md

## Quick Start

### Step 1: Create a Form on Forminit

1. Sign up or log in at [forminit.com](https://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`)

---

### Step 2: Install Forminit Package

```bash
npm install forminit
```

---

### Step 3: Create Form Component

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.

```tsx
// 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](https://forminit.com/docs/form-blocks/).

---

### Step 4: Use in Your Page

```tsx
// 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>
  );
}
```

---

## Form Field Reference

For a complete list of available form blocks (text, email, phone, file, rating, select, etc.) and field naming patterns, see the [Form Blocks documentation](https://forminit.com/docs/form-blocks/).

---

## Handling Form Submission Response

The SDK returns `{ data, redirectUrl, error }`:

**On success:**

```javascript
{
  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:**

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

---

## Redirect After Submission

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:

```tsx
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:

```tsx
import { useRouter } from 'next/navigation';

const router = useRouter();

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

---

## TypeScript Types

The Forminit SDK includes TypeScript definitions:

```typescript
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);
}
```

---

## Next Steps

1. [View your submissions](https://app.forminit.com) in the Forminit dashboard
2. Set up [email notifications](https://forminit.com/docs/email-notifications/) for new submissions
3. Add spam protection with [Cloudflare Turnstile](https://forminit.com/docs/cloudflare-turnstile/), [reCAPTCHA](https://forminit.com/docs/recaptcha/), or [hCaptcha](https://forminit.com/docs/hcaptcha/)
4. Explore [webhook integrations](https://forminit.com/docs/webhooks/) for advanced workflows
