skip to content

In a Laravel Blade form, why do you add @method('DELETE'), and what breaks when that spoofing is set up wrong?

level: middleimportance: should knowfreq 50%

answer

  1. HTML forms send GET or POST
  2. hidden _method input
  3. @method calls method_field()
  4. override read only on POST
  5. missing spoof: 405 with supported methods

basics

~20 s

HTML forms can only send GET or POST, so @method('DELETE') adds a hidden _method field; Laravel reads it on a POST request and routes the request as DELETE, which lets a form reach a Route::delete route.

solid answer

~40 s

Browsers submit forms only as GET or POST, but a recipe site's delete button should hit `Route::delete('/recipes/{recipe}', ...)`. The `@method('DELETE')` Blade directive compiles to `method_field('DELETE')`, which prints `<input type="hidden" name="_method" value="DELETE">`. Laravel enables HTTP method parameter override on every request, so a **POST** carrying `_method=DELETE` is treated as DELETE when the router matches it. Things break in predictable ways: forget `method="POST"` and the browser sends a GET, which never gets overridden; forget `@method` and the POST hits a DELETE-only URI and gets a 405 `The POST method is not supported for route ... Supported methods: DELETE.`; forget `@csrf` and the web group rejects the form. JavaScript clients do not need spoofing: they send DELETE directly.

code

php · 8 lines
php
<?php

// routes/web.php
use App\Http\Controllers\RecipeController;
use Illuminate\Support\Facades\Route;

Route::get('/recipes/{recipe}', [RecipeController::class, 'show'])->name('recipes.show');
Route::delete('/recipes/{recipe}', [RecipeController::class, 'destroy'])->name('recipes.destroy');

go deeper

for a junior

Remember that HTML forms send only GET or POST and that @method adds a hidden _method field to a POST form.

for a middle

Explain that the override is applied to POST requests only, what method_field() prints, and how a missing spoof shows up as a 405 naming the supported verbs.

for a senior

Diagnose spoofing bugs from the 405 message and the URL, keep destructive actions off GET, and know that script clients send real verbs.

for a principal

Decide whether server-rendered forms or script clients own write actions, and keep verb conventions consistent across web and API surfaces.

## Why spoofing exists REST-style routes use the HTTP verb to say what a request does: `GET /recipes/42` reads, `PUT` or `PATCH` updates, `DELETE` removes. HTML forms, however, only support **GET and POST** in their `method` attribute. **Method spoofing** bridges the gap: the form sends a POST and carries the verb it *means* in a hidden field named `_method`. ## What @method generates The Blade directive is a thin wrapper around a helper: - `@method('DELETE')` compiles to `<?php echo method_field('DELETE'); ?>`. - `method_field('DELETE')` returns `<input type="hidden" name="_method" value="DELETE">`. A complete delete button on a recipe page looks like this: ```html <form method="POST" action="{{ route('recipes.destroy', $recipe) }}"> @csrf @method('DELETE') <button type="submit">Delete recipe</button> </form> ``` `@csrf` belongs to the CSRF defence of web routes; it is shown because a spoofed form in `routes/web.php` still needs it. ## How Laravel applies the override 1. The HTTP kernel calls `$request->enableHttpMethodParameterOverride()` on each request (`Request::capture()` does the same), switching on the override that Laravel's request class inherits from Symfony's HttpFoundation. 2. When the real method is **POST** and the body carries `_method`, the request reports the spoofed verb from `getMethod()`. 3. The router matches using that verb, so the request reaches `Route::delete`, `Route::put` or `Route::patch`. The override applies only to POST requests. A GET link with `?_method=DELETE` stays a GET, which is exactly what you want: a crawler following links must never delete anything. ## Failure modes | Symptom | Cause | Fix | |---|---|---| | `405: The POST method is not supported for route recipes/42. Supported methods: DELETE.` | `@method` missing, so the router sees a plain POST | add `@method('DELETE')` | | The recipe page reloads, nothing is deleted, the URL now carries `_method` and `_token` in the query string | the `<form>` has no `method="POST"`, so it defaults to GET and matches the show route | set `method="POST"` | | `405 ... Supported methods: DELETE.` for a GET | same missing `method="POST"`, on a URI with only a delete route | set `method="POST"` | | 419 page expired | the CSRF token is missing or stale | include `@csrf` | The 405 happens during route matching, before any route middleware runs, so its message is the most direct clue: it names the verb the router actually saw. ## Why the verb matters beyond the router Spoofing is not cosmetic. Resource controllers, authorization rules written per action, route-level middleware and `route:list` audits all assume that deleting is a DELETE and updating is a PUT or PATCH. A codebase that sidesteps spoofing with extra POST endpoints ends up with two conventions, and reviewers can no longer tell from the verb alone whether a route changes data. ## Seeing the spoofed verb inside the app Once the override is applied, the rest of the framework sees only the spoofed verb: - `$request->method()` returns `DELETE` inside the controller, middleware and form request. - `route:list` shows the route under `DELETE`, which is how you confirm the form's target exists. - Edit forms usually spoof `PUT` or `PATCH`; pick the one your update route is registered with, or the router answers with a 405 naming the verbs it expected. A quick way to debug a stubborn form is to open the browser's network panel: the request line must say `POST`, and the form payload must contain `_method=DELETE` and `_token`. If the request line says `GET`, the problem is the `<form>` tag, not the route. ## When you do not need spoofing - **JavaScript clients** (fetch, axios, Inertia's router) can send PUT, PATCH and DELETE directly; no hidden field is needed. - **API routes** consumed by mobile or partner apps use real verbs. - **Tests** can call `$this->delete('/recipes/42')` directly; spoofing is purely an HTML-form workaround. ## Pitfalls interviewers probe - **Thinking `@method` changes the form's `method` attribute.** It only adds a hidden input; the `<form>` tag must still say `POST`. - **Thinking a GET link can be spoofed.** The override is read only from POST requests. - **Registering the delete endpoint as `Route::post('/recipes/{id}/delete')` to avoid spoofing.** It works, but it abandons the resource convention the rest of the app, `route:list` and resource controllers rely on. - **Believing spoofing replaces CSRF protection.** They solve unrelated problems; the form needs both.

  • Does a link to /recipes/42?_method=DELETE delete the recipe in a Laravel app?
    No. The request is a GET, and the method override is honoured only for POST requests, so the router matches the GET route (the show page) or returns a 405 if none exists. That is deliberate: destructive actions must never be reachable through a link that crawlers or prefetchers might follow.
  • How would an Inertia or fetch-based page call the same Route::delete route?
    It sends a real DELETE request, for example `fetch(url, { method: 'DELETE', headers: {...} })` or Inertia's `router.delete(url)`. Spoofing exists only because the HTML `<form>` element cannot express DELETE; script clients can, so they need no `_method` field.

saying these in an interview costs you the question

  • @method('DELETE') works on a form declared with method GET
  • Laravel reads _method on any request, including GET links
  • Method spoofing makes the CSRF token unnecessary
  • @method rewrites the form tag's method attribute in the browser
  • A missing @method produces a 404 Not Found