React Server Actions are server functions that handle form submissions in Next.js. They receive FormData automatically when used as a form action.
Define a Server Action inline in a Server Component:
import { auth } from '@/lib/auth'
export default function Page() {
async function createInvoice(formData: FormData) {
'use server'
const session = await auth()
if (!session?.user) throw new Error('Unauthorized')
const rawFormData = {
customerId: formData.get('customerId'),
amount: formData.get('amount'),
status: formData.get('status'),
}
// mutate data
// revalidate the cache
}
return <form action={createInvoice}>...</form>
}
For forms with many fields, use Object.fromEntries(formData) to extract all values at once.
Passing additional arguments
Use JavaScript’s bind to pass additional arguments to a Server Action:
'use client'
import { updateUser } from './actions'
export function UserProfile({ userId }: { userId: string }) {
const updateUserWithId = updateUser.bind(null, userId)
return (
<form action={updateUserWithId}>
<input type="text" name="name" />
<button type="submit">Update User Name</button>
</form>
)
}
'use server'
export async function updateUser(userId: string, formData: FormData) {}
bind works in both Server and Client Components and supports progressive enhancement.
Use HTML attributes for basic validation:<input type="email" name="email" required />
<input type="text" name="name" minLength={2} />
Use Zod for comprehensive server-side validation:'use server'
import { z } from 'zod'
const schema = z.object({
email: z.string({
invalid_type_error: 'Invalid Email',
}),
})
export default async function createUser(formData: FormData) {
const validatedFields = schema.safeParse({
email: formData.get('email'),
})
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
// Mutate data
}
Validation errors and pending states
Use useActionState to display validation errors and track pending state in a Client Component:
'use server'
import { z } from 'zod'
export async function createUser(initialState: any, formData: FormData) {
const validatedFields = schema.safeParse({
email: formData.get('email'),
})
// ...
}
'use client'
import { useActionState } from 'react'
import { createUser } from '@/app/actions'
const initialState = { message: '' }
export function Signup() {
const [state, formAction, pending] = useActionState(createUser, initialState)
return (
<form action={formAction}>
<label htmlFor="email">Email</label>
<input type="text" id="email" name="email" required />
<p aria-live="polite">{state?.message}</p>
<button disabled={pending}>Sign up</button>
</form>
)
}
For a reusable submit button component, use useFormStatus:
'use client'
import { useFormStatus } from 'react-dom'
export function SubmitButton() {
const { pending } = useFormStatus()
return (
<button disabled={pending} type="submit">
Sign Up
</button>
)
}
import { SubmitButton } from './button'
import { createUser } from '@/app/actions'
export function Signup() {
return (
<form action={createUser}>
{/* Other form elements */}
<SubmitButton />
</form>
)
}
In React 19, useFormStatus returns additional keys: data, method, and action. In earlier versions, only pending is available.
Optimistic updates
Use useOptimistic to update the UI immediately before the server responds:
'use client'
import { useOptimistic } from 'react'
import { send } from './actions'
type Message = { message: string }
export function Thread({ messages }: { messages: Message[] }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic<Message[], string>(
messages,
(state, newMessage) => [...state, { message: newMessage }]
)
const formAction = async (formData: FormData) => {
const message = formData.get('message') as string
addOptimisticMessage(message)
await send(message)
}
return (
<div>
{optimisticMessages.map((m, i) => (
<div key={i}>{m.message}</div>
))}
<form action={formAction}>
<input type="text" name="message" />
<button type="submit">Send</button>
</form>
</div>
)
}
Call Server Actions from nested <button>, <input type="submit">, and <input type="image"> elements using the formAction prop. This allows multiple actions within a single form:
<form action={saveDraft}>
<input type="text" name="content" />
<button type="submit">Save Draft</button>
<button formAction={publishPost} type="submit">Publish</button>
</form>
Programmatic submission
Trigger form submission programmatically using requestSubmit():
'use client'
export function Entry() {
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if ((e.ctrlKey || e.metaKey) && (e.key === 'Enter' || e.key === 'NumpadEnter')) {
e.preventDefault()
e.currentTarget.form?.requestSubmit()
}
}
return (
<div>
<textarea name="entry" rows={20} required onKeyDown={handleKeyDown} />
</div>
)
}