Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/vercel/next.js/llms.txt

Use this file to discover all available pages before exploring further.

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