nextjs-onsubmit
Next.js
Drop-in Next.js component and hook for onsubmit.dev. Works with both App Router and Pages Router — no backend required.
Installation
bash
npm install nextjs-onsubmitQuick start
App Router
tsx
// app/contact/page.tsx
import { OnSubmitForm } from 'nextjs-onsubmit';
export default function ContactPage() {
return (
<OnSubmitForm
formId="YOUR_FORM_ID"
successMessage="Thanks! We'll be in touch."
>
<input name="name" placeholder="Your name" required />
<input name="email" type="email" placeholder="Email" required />
<textarea name="message" placeholder="Message" />
<button type="submit">Send</button>
</OnSubmitForm>
);
}Pages Router
tsx
// pages/contact.tsx
import { OnSubmitForm } from 'nextjs-onsubmit';
export default function ContactPage() {
return (
<OnSubmitForm
formId="YOUR_FORM_ID"
successMessage="Thanks! We'll be in touch."
>
<input name="name" placeholder="Your name" required />
<input name="email" type="email" placeholder="Email" required />
<button type="submit">Send</button>
</OnSubmitForm>
);
}Your form ID is available in the onsubmit.dev dashboard after creating a form.
useOnSubmit hook
For full control over the form UI, use the hook directly:
tsx
'use client';
import { useOnSubmit } from 'nextjs-onsubmit';
export function ContactForm() {
const { handleSubmit, isLoading, isSuccess, error, reset } = useOnSubmit({
formId: 'YOUR_FORM_ID',
onSuccess: (data) => console.log('Submitted!', data),
});
if (isSuccess) return <p>Thanks! We'll be in touch.</p>;
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<button type="submit" disabled={isLoading}>
{isLoading ? 'Sending…' : 'Send'}
</button>
{error && <p>{error}</p>}
</form>
);
}Router redirects
If the API response includes a redirect URL, pass router.push via onRedirect to navigate using the Next.js router instead of a hard redirect:
tsx
'use client';
import { useRouter } from 'next/navigation';
import { OnSubmitForm } from 'nextjs-onsubmit';
export function ContactForm() {
const router = useRouter();
return (
<OnSubmitForm formId="YOUR_FORM_ID" onRedirect={router.push}>
<input name="email" type="email" required />
<button type="submit">Send</button>
</OnSubmitForm>
);
}Component props
OnSubmitForm accepts the following props. All standard HTML <form> attributes are also accepted.
| Prop | Type | Description |
|---|---|---|
| formId | string | Required. Your onsubmit.dev form ID. |
| successMessage | string | Text to show in place of the form on success. |
| endpoint | string | Override the default API endpoint. |
| onSuccess | (data) => void | Callback fired on successful submission. |
| onError | (error) => void | Callback fired on submission error. |
| onRedirect | (url: string) => void | Called when the API returns a redirect URL. |
Hook API
Options
| Option | Type | Description |
|---|---|---|
| formId | string | Required. Your onsubmit.dev form ID. |
| endpoint | string | Override the default API endpoint. |
| onSuccess | (data) => void | Callback fired on successful submission. |
| onError | (error) => void | Callback fired on submission error. |
| onRedirect | (url: string) => void | Called when the API returns a redirect URL. |
Returns
| Value | Type | Description |
|---|---|---|
| handleSubmit | (e) => Promise<void> | Pass to the form's onSubmit. |
| isLoading | boolean | true while the request is in flight. |
| isSuccess | boolean | true after a successful submission. |
| error | string | null | Error message, or null. |
| data | unknown | Raw response data. |
| reset | () => void | Resets all state back to initial values. |
File uploads
File inputs are handled automatically. When the form contains a file field the data is sent as multipart/form-data. Otherwise, JSON is used.