Skip to content

method-override-middleware

The methodOverride middleware reads a hidden _method field from form data and overrides the request method accordingly. This enables HTML forms to submit PUT, PATCH, and DELETE requests, even though HTML forms only natively support GET and POST.

Installation

The method override middleware is included with Remix. No additional installation is needed.

Import

ts
import { methodOverride } from 'remix/method-override-middleware'

API

methodOverride(options?)

Returns a middleware function that reads the _method field from parsed form data and overrides the request method.

ts
let router = createRouter({
  middleware: [
    formData(),
    methodOverride(),
  ],
})

formData must come first

The methodOverride middleware reads from the parsed form data in the context. The formData middleware must appear before methodOverride in the middleware chain, otherwise there is no parsed form data to read.

Options

OptionTypeDefaultDescription
fieldNamestring'_method'The form field name that contains the intended HTTP method.
methodsstring[]['PUT', 'PATCH', 'DELETE']The methods that are allowed as override values. Any other value in the field is ignored.

How It Works

  1. The middleware checks if the request method is POST (the only method that can carry a form body in HTML).
  2. It reads the value of the _method field from the parsed form data.
  3. If the value is one of the allowed methods (PUT, PATCH, or DELETE by default), the request method is changed to that value.
  4. The _method field is removed from the form data so handlers do not see it.

This means your route handlers can use RESTful methods naturally:

ts
// This handler responds to DELETE requests,
// including those from forms using _method="DELETE"
router.map(deletePostRoute, ({ params }) => {
  await db.delete(posts, { where: eq(posts.columns.id, params.id) })
  return redirect('/posts')
})

Why Method Override Is Needed

HTML forms only support two methods: GET and POST. There is no way to set method="DELETE" or method="PUT" on a <form> element. Without method override, you must route all mutations through POST and distinguish operations by URL or hidden fields.

With method override, you can design routes using standard REST conventions:

ActionMethodURL
List postsGET/posts
Create postPOST/posts
Update postPUT/posts/:id
Delete postDELETE/posts/:id

Examples

Basic Form with DELETE

html
<form method="post" action="/posts/42">
  <input type="hidden" name="_method" value="DELETE" />
  <button type="submit">Delete Post</button>
</form>

The middleware changes the request method from POST to DELETE before the route handler runs.

RestfulForm Pattern

Create a reusable component pattern for RESTful forms:

ts
function restfulForm(method: string, action: string, content: string) {
  let hiddenField = method !== 'POST'
    ? `<input type="hidden" name="_method" value="${method}" />`
    : ''

  return `
    <form method="post" action="${action}">
      ${hiddenField}
      ${content}
    </form>
  `
}

// Usage
return html`
  ${new SafeHtml(restfulForm('DELETE', `/posts/${post.id}`, `
    <button type="submit">Delete</button>
  `))}

  ${new SafeHtml(restfulForm('PUT', `/posts/${post.id}`, `
    <input type="text" name="title" value="${post.title}" />
    <button type="submit">Update</button>
  `))}
`

Custom Field Name

ts
methodOverride({
  fieldName: '_httpMethod',
})
html
<form method="post" action="/posts/42">
  <input type="hidden" name="_httpMethod" value="PUT" />
  <!-- ... -->
</form>

Middleware Order

The recommended order places formData before methodOverride:

ts
let router = createRouter({
  middleware: [
    logger(),
    compression(),
    staticFiles('./public'),
    csrf(),
    formData(),
    methodOverride(),
    session(sessionCookie, sessionStorage),
  ],
})

Released under the MIT License.