Skip to main content
Next.js App Router is a great fit for Formbase because server actions can submit on the server and return a clean JSON response. This avoids browser redirects and gives you full control over the UI. You can still submit from the client if you want, but server actions are the most reliable pattern.
1

Create a server action

Submit the form data from the server so Formbase returns JSON instead of a redirect.
// app/actions/submit-to-formbase.ts
'use server';

export async function submitToFormbase(
  _prevState: { ok: boolean; message: string },
  formData: FormData,
) {
  const response = await fetch('https://formbase.dev/s/YOUR_FORM_ID', {
    method: 'POST',
    body: formData,
  });

  if (!response.ok) {
    return { ok: false, message: 'Submission failed.' };
  }

  return { ok: true, message: 'Submitted!' };
}
This works for both text fields and file uploads.
2

Wire it to a client component

'use client';

import { useFormState } from 'react-dom';
import { submitToFormbase } from '../actions/submit-to-formbase';

const initialState = { ok: false, message: '' };

export default function ContactForm() {
  const [state, formAction] = useFormState(submitToFormbase, initialState);

  return (
    <form action={formAction}>
      <input name="name" placeholder="Name" required />
      <input name="email" type="email" placeholder="Email" required />
      <textarea name="message" required />
      <button type="submit">Send</button>
      {state.message && <p>{state.message}</p>}
    </form>
  );
}
The form posts to your server action, which forwards the submission to Formbase.

Client-side alternative

If you must submit from the browser, treat a 303 response as success:
const response = await fetch('https://formbase.dev/s/YOUR_FORM_ID', {
  method: 'POST',
  body: new FormData(event.currentTarget),
  redirect: 'manual',
});

if (response.status === 303 || response.ok) {
  // success
}
Server actions avoid CORS issues and give you a JSON response. Use them when possible.