In a Laravel Blade form, why do you add @method('DELETE'), and what breaks when that spoofing is set up wrong?
answer
- HTML forms send GET or POST
- hidden _method input
- @method calls method_field()
- override read only on POST
- missing spoof: 405 with supported methods
basics
~20 sHTML 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 sBrowsers 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
// 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
Remember that HTML forms send only GET or POST and that @method adds a hidden _method field to a POST form.
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.
Diagnose spoofing bugs from the 405 message and the URL, keep destructive actions off GET, and know that script clients send real verbs.
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