<Form>

Submits a form through client-side navigation, prefetching the destination and working without JavaScript.

Signature
import Form from 'next/form'

// action as a string (GET-style navigation)
<Form
  action={string}      // required - URL or relative path
  replace={boolean}    // default: false
  scroll={boolean}     // default: true
  prefetch={boolean}   // default: true
>
  {children}
</Form>

// action as a Server Function
<Form action={(formData: FormData) => void | Promise<void>}>
  {children}
</Form>
  • Added in Next.js v15.0.0

Parameters

ParameterTypeDescription
$actionstring | ((formData: FormData) => void | Promise<void>)A URL or relative path to navigate to on submit - an empty string means the current route with updated search params - or a Server Function to execute. The prop's type decides the whole component's behaviour.
$replaceoptionalboolean= falseReplaces the current history entry instead of pushing a new one. Ignored when action is a function.
$scrolloptionalboolean= trueControls scroll behaviour during the navigation. Ignored when action is a function.
$prefetchoptionalboolean= trueWhether to prefetch the path once the form enters the viewport. Only meaningful for a string action, since a function action's destination is unknown until it runs.
$onSubmitoptional(event: React.FormEvent) => voidStandard submit handler. Calling event.preventDefault() overrides the navigation this component exists to perform.

Return value

JSX.ElementA <form> element that navigates client-side on submit when action is a string, or invokes the Server Function when action is a function.

Errors

Passing a key prop alongside a string action is unsupported, and method, encType, target, formMethod, formEncType or formTarget fall back to native browser behaviour instead of the component's navigation.

Changelog

  • PHP v15.0.0next/form introduced with prefetching, client-side navigation on submit and progressive enhancement.

<Form> is a <form> with the router attached. Its behaviour depends entirely on the type of action.

String action

It behaves like a native GET form: field values are encoded into the URL as search params. On top of that, Next.js prefetches the destination — including its layout.js and loading.js — as soon as the form scrolls into view, and performs a client-side navigation on submit so shared layouts and client state survive. If JavaScript has not loaded yet, the browser does a normal full-page submit and the feature still works.

Function action

It behaves like a React form and runs the Server Function. replace and scroll are ignored, and nothing is prefetched, because the destination is not known until the action executes. Redirect from inside the action instead.

What it will not do

method, encType and target are unsupported — they would override the whole point of the component, so use a plain <form> if you need them. A key on a string-action form is unsupported too. And <input type="file"> with a string action submits only the filename, matching native GET behaviour.

Example

Example · typescript
import Form from 'next/form'

export default function Search() {
  return (
    <Form action="/search">
      {/* On submission, the input value is appended to the URL,
          e.g. /search?query=abc */}
      <input name="query" />
      <button type="submit">Submit</button>
    </Form>
  )
}

When to use it

  • Build a search box that navigates to /search?query=... while keeping the surrounding layout mounted.
  • Filter a list in place by passing an empty string action to update search params on the current route.
  • Submit a create-post form to a Server Function and redirect to the new record.
  • Keep a form usable before hydration finishes on a slow connection.

Frequently asked questions

Why is my form doing nothing when I submit it?

An onSubmit handler is almost certainly calling event.preventDefault(), which cancels the navigation <Form> would perform. Remove it, or switch to a plain <form> if you want to handle submission yourself.

Can I use method="POST" with next/form?

No - method, encType and target are unsupported because they override the component's navigation behaviour. Pass a Server Function to action for a POST-style mutation, or use the HTML <form> element.

Does <Form> prefetch when the action is a Server Function?

No. The destination is only known once the action runs, so nothing can be prefetched; the replace and scroll props are ignored in that mode too.

More examples

The results page reading the search params

The name attribute on each input becomes the search param key, so <input name="query" /> arrives here as searchParams.query. On Next.js 15 and later searchParams is a Promise and must be awaited - reading it synchronously is the most common error when wiring up a <Form> destination.

Example · typescript
import { getSearchResults } from '@/lib/search'

export default async function SearchPage({
  searchParams,
}: {
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}) {
  const { query } = await searchParams
  const results = await getSearchResults(query)

  return (
    <ul>
      {results.map((result) => (
        <li key={result.id}>{result.title}</li>
      ))}
    </ul>
  )
}

Mutating with a Server Function

With a function action the component stops managing the URL: no prefetch, no replace, no scroll. The navigation comes from redirect() inside the action, which is why the destination cannot be prefetched - it does not exist until the row is written.

Example · typescript
import Form from 'next/form'
import { createPost } from '@/posts/actions'

export default function NewPostPage() {
  return (
    <Form action={createPost}>
      <input name="title" />
      <textarea name="body" />
      <button type="submit">Create Post</button>
    </Form>
  )
}

// app/posts/actions.ts
// 'use server'
// import { redirect } from 'next/navigation'
//
// export async function createPost(formData: FormData) {
//   const post = await db.posts.create({
//     title: String(formData.get('title')),
//     body: String(formData.get('body')),
//   })
//   redirect(`/posts/${post.id}`)
// }

Instant feedback with useFormStatus

Prefetching covers the shared layout and loading.js, but there is still a gap while the results themselves are fetched. useFormStatus must be called from a component rendered inside the form - reading it in the same component that renders <Form> always returns pending: false.

Example · typescript
'use client'

import { useFormStatus } from 'react-dom'

export function SearchButton() {
  const { pending } = useFormStatus()

  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Searching...' : 'Search'}
    </button>
  )
}

Discussion

  • Be the first to comment on this lesson.