← All Guides

Next.js Forms

Add codaForms to your Next.js app. Works with App Router, Pages Router, and Server Actions.

5 minutes Intermediate

Prerequisites

  • A codaForms account (sign up free)
  • A Next.js 13+ application

App Router (Recommended)

For Next.js 13+ with the app directory.

Client Component

Create a client component for the form:

app/components/ContactForm.tsx

'use client';

import { useState } from 'react';

export default function ContactForm() {
  const [status, setStatus] = useState<'idle' | 'submitting' | 'success' | 'error'>('idle');
  const FORM_ID = 'cf_xxxxx';

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

    const formData = new FormData(e.currentTarget);
    const data = {
      _formId: FORM_ID,
      name: formData.get('name'),
      email: formData.get('email'),
      message: formData.get('message'),
    };

    try {
      const res = await fetch('https://codasite.ai/forms/api/submit', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
      });

      const result = await res.json();
      setStatus(result.success ? 'success' : 'error');
    } catch {
      setStatus('error');
    }
  }

  if (status === 'success') {
    return (
      <div className="p-6 bg-green-500/10 rounded-lg text-center">
        <p className="text-green-400">Thanks! We'll be in touch soon.</p>
      </div>
    );
  }

  return (
    <form onSubmit={handleSubmit} className="space-y-4">
      <input
        name="name"
        placeholder="Name"
        required
        className="w-full p-3 rounded-lg bg-gray-800 border border-gray-700"
      />
      <input
        name="email"
        type="email"
        placeholder="Email"
        required
        className="w-full p-3 rounded-lg bg-gray-800 border border-gray-700"
      />
      <textarea
        name="message"
        placeholder="Message"
        rows={4}
        required
        className="w-full p-3 rounded-lg bg-gray-800 border border-gray-700"
      />
      <button
        type="submit"
        disabled={status === 'submitting'}
        className="w-full p-3 bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-50"
      >
        {status === 'submitting' ? 'Sending...' : 'Send Message'}
      </button>
      {status === 'error' && (
        <p className="text-red-400 text-sm">Something went wrong. Please try again.</p>
      )}
    </form>
  );
}

With Server Actions (Next.js 14+)

Use Server Actions for a more secure approach:

app/actions/submit-form.ts

'use server';

export async function submitForm(formData: FormData) {
  const FORM_ID = 'cf_xxxxx';

  const data = {
    _formId: FORM_ID,
    name: formData.get('name'),
    email: formData.get('email'),
    message: formData.get('message'),
  };

  const res = await fetch('https://codasite.ai/forms/api/submit', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
  });

  const result = await res.json();
  return result;
}

app/contact/page.tsx

'use client';

import { useFormStatus } from 'react-dom';
import { submitForm } from '../actions/submit-form';

function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Sending...' : 'Send Message'}
    </button>
  );
}

export default function ContactPage() {
  return (
    <form action={submitForm}>
      <input name="name" placeholder="Name" required />
      <input name="email" type="email" placeholder="Email" required />
      <textarea name="message" placeholder="Message" required />
      <SubmitButton />
    </form>
  );
}

Pages Router (Legacy)

For Next.js 12 or apps using the pages directory.

pages/contact.tsx

import { useState, FormEvent } from 'react';

export default function Contact() {
  const [status, setStatus] = useState('idle');
  const FORM_ID = 'cf_xxxxx';

  async function handleSubmit(e: FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setStatus('submitting');

    const form = e.currentTarget;
    const formData = new FormData(form);

    const res = await fetch('https://codasite.ai/forms/api/submit', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        _formId: FORM_ID,
        name: formData.get('name'),
        email: formData.get('email'),
        message: formData.get('message'),
      }),
    });

    const result = await res.json();
    setStatus(result.success ? 'success' : 'error');
  }

  if (status === 'success') {
    return <p>Thanks! We'll be in touch.</p>;
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" placeholder="Name" required />
      <input name="email" type="email" placeholder="Email" required />
      <textarea name="message" placeholder="Message" required />
      <button disabled={status === 'submitting'}>
        {status === 'submitting' ? 'Sending...' : 'Send'}
      </button>
    </form>
  );
}

Ready to get started?

Create your first form in under 2 minutes.

Create Free Account