Skip to main content
React Server Actions are server functions that handle form submissions in Next.js. They receive FormData automatically when used as a form action.
Always verify authentication and authorization inside each Server Action, even if the form is rendered only on an authenticated page.

Basic form submission

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.

Form validation

Use HTML attributes for basic validation:
<input type="email" name="email" required />
<input type="text" name="name" minLength={2} />

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>
  )
}

Submit button with useFormStatus

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>
  )
}

Nested form elements

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>
  )
}

Build docs developers (and LLMs) love