Handle form submissions with Next.js
![]()
Copy AI Instructions
Copy and paste into ChatGPT, Claude, Cursor, or your preferred AI assistant. Includes everything needed to generate forms with Forminit.
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
Quick Start
Section titled “Quick Start”Step 1: Create a Form on Forminit
Section titled “Step 1: Create a Form on Forminit”- Sign up or log in at forminit.com
- Create a new form
- Go to Form Settings → Set authentication mode to Protected
- Copy your Form ID (e.g.,
YOUR-FORM-ID)
Step 2: Install Forminit Package
Section titled “Step 2: Install Forminit Package”npm install forminit
Step 3: Set Environment Variable
Section titled “Step 3: Set Environment Variable”- Create an API token from Account → API Tokens in your Forminit dashboard
- Add the token to
.env.local:
FORMINIT_API_KEY=sk_live_xxxxxxxxxxxx
Step 4: Create API Route (App Router)
Section titled “Step 4: Create API Route (App Router)”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;
Step 5: Create Form Component
Section titled “Step 5: Create Form Component”// 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>
);
}
Step 6: Use in Your Page
Section titled “Step 6: Use in Your Page”// 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>
);
}
Using Environment Variables for Form ID
Section titled “Using Environment Variables for Form ID”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);
Form Field Reference
Section titled “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.
Complete Examples
Section titled “Complete Examples”Contact Form with Subject Selection
Section titled “Contact Form with Subject Selection”// 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>
);
}
Newsletter Signup
Section titled “Newsletter Signup”// 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>
);
}
Feedback Form with Rating
Section titled “Feedback Form with Rating”// 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>
);
}
Job Application with File Upload
Section titled “Job Application with File Upload”// 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>
);
}
JSON Submission (Alternative)
Section titled “JSON Submission (Alternative)”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',
},
],
});
Handling Form Submission Response
Section titled “Handling Form Submission Response”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"
}
}
Redirect After Submission
Section titled “Redirect After Submission”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>
);
}
Project Structure Example
Section titled “Project Structure Example”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
TypeScript Types
Section titled “TypeScript Types”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);
}
Next Steps
Section titled “Next Steps”- View your submissions in the Forminit dashboard
- Set up email notifications for new submissions
- Explore webhook integrations for advanced workflows