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-nextjs/SKILL.md

Next.js is a React framework that supports both static site generation (SSG) and server-side rendering (SSR). Forminit integrates seamlessly with Next.js through our NPM package, which provides a secure server-side proxy to protect your API keys.

What you’ll get:

  • Accept form submissions without building a backend
  • Email notifications for new submissions
  • Spam protection (reCAPTCHA, hCaptcha, honeypot)
  • File uploads support
  • Submission dashboard to manage responses

Requirements:

  • Next.js 13+
  • Node.js 18+
  • A Forminit account and Form ID

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

npm install forminit

  1. Create an API token from Account → API Tokens in your Forminit dashboard
  2. Add the token to .env.local:
FORMINIT_API_KEY=sk_live_xxxxxxxxxxxx

Create a proxy route that securely forwards requests to Forminit:

// app/api/forminit/route.ts

import { createForminitProxy } from 'forminit/next';

const forminit = createForminitProxy({
  apiKey: process.env.FORMINIT_API_KEY,
});

export const POST = forminit.POST;

// components/ContactForm.tsx

'use client';

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

const forminit = new Forminit({ proxyUrl: '/api/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('Xk9mP2nQ1r', formData);

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

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

  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <div className="form-group">
        <label htmlFor="firstName">First Name</label>
        <input
          type="text"
          id="firstName"
          name="fi-sender-firstName"
          placeholder="John"
          required
        />
      </div>

      <div className="form-group">
        <label htmlFor="lastName">Last Name</label>
        <input
          type="text"
          id="lastName"
          name="fi-sender-lastName"
          placeholder="Doe"
          required
        />
      </div>

      <div className="form-group">
        <label htmlFor="email">Email</label>
        <input
          type="email"
          id="email"
          name="fi-sender-email"
          placeholder="john@example.com"
          required
        />
      </div>

      <div className="form-group">
        <label htmlFor="message">Message</label>
        <textarea
          id="message"
          name="fi-text-message"
          placeholder="Your message..."
          rows={5}
          required
        />
      </div>

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

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

Store your Form ID in environment variables:

# .env.local

FORMINIT_API_KEY=sk_live_xxxxxxxxxxxx
NEXT_PUBLIC_FORMINIT_FORM_ID=Xk9mP2nQ1r

Access in your component:

const FORM_ID = process.env.NEXT_PUBLIC_FORMINIT_FORM_ID!;

const { data, error } = await forminit.submit(FORM_ID, formData);

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


// components/ContactFormWithSubject.tsx

'use client';

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

const forminit = new Forminit({ proxyUrl: '/api/forminit' });

export function ContactFormWithSubject() {
  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');

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

    if (error) {
      setStatus('error');
      setErrorMessage(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 />
      
      <select name="fi-select-subject" required>
        <option value="">Select a subject</option>
        <option value="general">General Inquiry</option>
        <option value="support">Support</option>
        <option value="sales">Sales</option>
        <option value="partnership">Partnership</option>
      </select>
      
      <textarea name="fi-text-message" placeholder="Your message" required />
      
      {status === 'error' && <p className="status-error">{errorMessage}</p>}
      {status === 'success' && <p className="status-success">Message sent!</p>}
      
      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Sending...' : 'Send'}
      </button>
    </form>
  );
}
// components/NewsletterForm.tsx

'use client';

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

const forminit = new Forminit({ proxyUrl: '/api/forminit' });

export function NewsletterForm() {
  const [email, setEmail] = useState('');
  const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
  const [errorMessage, setErrorMessage] = useState<string | null>(null);

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setStatus('loading');

    const formData = new FormData();
    formData.append('fi-sender-email', email);

    const { error } = await forminit.submit('aB3cD5eF7g', formData);

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

    setStatus('success');
    setEmail('');
  }

  return (
    <form onSubmit={handleSubmit} className="newsletter-form">
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Enter your email"
        required
      />
      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Subscribing...' : 'Subscribe'}
      </button>
      
      {status === 'error' && <p className="status-error">{errorMessage}</p>}
      {status === 'success' && <p className="status-success">Thank you for subscribing!</p>}
    </form>
  );
}
// components/FeedbackForm.tsx

'use client';

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

const forminit = new Forminit({ proxyUrl: '/api/forminit' });

export function FeedbackForm() {
  const [rating, setRating] = useState<number | null>(null);
  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');

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

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

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

  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <input type="email" name="fi-sender-email" placeholder="Email (optional)" />
      
      <fieldset>
        <legend>How would you rate your experience?</legend>
        {[1, 2, 3, 4, 5].map((n) => (
          <label key={n}>
            <input
              type="radio"
              name="fi-rating-experience"
              value={n}
              checked={rating === n}
              onChange={() => setRating(n)}
            />
            {n}
          </label>
        ))}
      </fieldset>
      
      <textarea name="fi-text-feedback" placeholder="Tell us more (optional)" />
      
      {status === 'error' && <p className="status-error">{errorMessage}</p>}
      {status === 'success' && <p className="status-success">Thank you for your feedback!</p>}
      
      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Submitting...' : 'Submit Feedback'}
      </button>
    </form>
  );
}
// components/ApplicationForm.tsx

'use client';

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

const forminit = new Forminit({ proxyUrl: '/api/forminit' });

export function ApplicationForm() {
  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');

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

    if (error) {
      setStatus('error');
      setErrorMessage(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 />
      <input type="tel" name="fi-sender-phone" placeholder="Phone (+1234567890)" />
      
      <input type="url" name="fi-url-linkedin" placeholder="LinkedIn profile URL" />
      <input type="url" name="fi-url-portfolio" placeholder="Portfolio URL" />
      
      <select name="fi-select-position" required>
        <option value="">Select position</option>
        <option value="frontend">Frontend Developer</option>
        <option value="backend">Backend Developer</option>
        <option value="fullstack">Full Stack Developer</option>
        <option value="designer">UI/UX Designer</option>
      </select>
      
      <label>
        Resume (PDF)
        <input type="file" name="fi-file-resume" accept=".pdf" required />
      </label>
      
      <label>
        Cover Letter (optional)
        <input type="file" name="fi-file-cover_letter" accept=".pdf,.doc,.docx" />
      </label>
      
      <textarea name="fi-text-why_join" placeholder="Why do you want to join us?" />
      
      {status === 'error' && <p className="status-error">{errorMessage}</p>}
      {status === 'success' && <p className="status-success">Application submitted!</p>}
      
      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Submitting...' : 'Submit Application'}
      </button>
    </form>
  );
}

Instead of FormData, you can submit structured JSON:

const { data, error } = await forminit.submit('Xk9mP2nQ1r', {
  blocks: [
    {
      type: 'sender',
      properties: {
        email: 'john@example.com',
        firstName: 'John',
        lastName: 'Doe',
      },
    },
    {
      type: 'text',
      name: 'message',
      value: 'Hello world',
    },
    {
      type: 'select',
      name: 'subject',
      value: 'general',
    },
  ],
});

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"
  }
}

If you prefer to redirect users to a thank-you page:

import { useRouter } from 'next/navigation';

export function ContactForm() {
  const router = useRouter();
  
  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    
    const formData = new FormData(e.currentTarget);
    const { data, redirectUrl, error } = await forminit.submit(FORM_ID, formData);

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

    // Redirect to thank-you page
    router.push('/thank-you');
    // Or use Forminit's redirect URL:
    // window.location.href = redirectUrl;
  }
  
  // ...
}

Create a thank-you page:

// app/thank-you/page.tsx

import Link from 'next/link';

export default function ThankYouPage() {
  return (
    <div>
      <h1>Thank You!</h1>
      <p>Thank you for your message! We'll get back to you soon.</p>
      <Link href="/">← Back to Home</Link>
    </div>
  );
}

Here’s a typical Next.js project structure with Forminit forms:

my-nextjs-app/
├── app/
│   ├── api/
│   │   └── forminit/
│   │       └── route.ts           # Forminit proxy route
│   ├── contact/
│   │   └── page.tsx               # Contact page
│   ├── thank-you/
│   │   └── page.tsx               # Thank you page
│   ├── layout.tsx                 # Root layout
│   └── page.tsx                   # Homepage
├── components/
│   ├── ContactForm.tsx            # Contact form component
│   ├── NewsletterForm.tsx         # Newsletter component
│   └── FeedbackForm.tsx           # Feedback component
├── .env.local                     # Environment variables
├── next.config.js                 # Next.js config
├── package.json
└── tsconfig.json

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