~3 min read • Updated Oct 27, 2025
Introduction
In Next.js, you can submit forms using Server Actions, which execute directly on the server. This eliminates the need for separate API routes and simplifies data handling.
How It Works
Use the action attribute on a form to invoke a Server Function. The function automatically receives a FormData object:
export default function Page() {
async function createInvoice(formData: FormData) {
'use server'
const rawFormData = {
customerId: formData.get('customerId'),
amount: formData.get('amount'),
status: formData.get('status'),
}
// mutate data
}
return <form action={createInvoice}>...</form>
}Passing Additional Arguments
You can pass extra arguments using bind:
'use client'
const updateUserWithId = updateUser.bind(null, userId)
<form action={updateUserWithId}>
<input name="name" />
</form>On the server:
'use server'
export async function updateUser(userId: string, formData: FormData) {}Form Validation
Use libraries like zod for server-side validation:
'use server'
import { z } from 'zod'
const schema = z.object({
email: z.string().email(),
})
export async function createUser(formData: FormData) {
const validated = schema.safeParse({
email: formData.get('email'),
})
if (!validated.success) {
return {
errors: validated.error.flatten().fieldErrors,
}
}
// mutate data
}Displaying Validation Errors with useActionState
Use useActionState in a Client Component to manage form state:
'use client'
import { useActionState } from 'react'
const initialState = { message: '' }
export function Signup() {
const [state, formAction, pending] = useActionState(createUser, initialState)
return (
<form action={formAction}>
<input name="email" required />
<p>{state?.message}</p>
<button disabled={pending}>Sign up</button>
</form>
)
}Managing Submission State
Use useFormStatus to show loading indicators:
'use client'
import { useFormStatus } from 'react-dom'
export function SubmitButton() {
const { pending } = useFormStatus()
return <button disabled={pending}>Sign Up</button>
}Optimistic UI Updates with useOptimistic
Update the UI before the server responds:
'use client'
import { useOptimistic } from 'react'
const [optimisticMessages, addMessage] = useOptimistic(messages, (state, msg) => [...state, { message: msg }])
const formAction = async (formData: FormData) => {
const msg = formData.get('message')
addMessage(msg)
await send(msg)
}Programmatic Form Submission
Use requestSubmit() to trigger form submission manually:
'use client'
const handleKeyDown = (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault()
e.currentTarget.form?.requestSubmit()
}
}Conclusion
Server Actions in Next.js offer a secure and elegant way to handle form submissions. With built-in support for validation, state management, and optimistic updates, you can build interactive and reliable forms with ease.
Written & researched by Dr. Shahin Siami