Route::delete
Registers a route that answers DELETE requests for removing a resource.
delete(string $uri, array|string|callable $action = null): Illuminate\Routing\RouteParameters
| Parameter | Type | Description |
|---|---|---|
$$uri | string | The URI pattern to match, usually ending in the resource identifier as a {param} segment. |
$$actionoptional | array|string|callable= null | A closure, a [Controller::class, 'method'] array, an invokable controller class name, or a 'Controller@method' string. |
Return value
Illuminate\Routing\RouteThe registered Route instance, so name(), middleware(), where() and the rest can be chained.
Errors
Nothing at registration. At request time an unmatched implicit binding returns 404 and a web form without @csrf returns 419.
Route::delete registers the destroy half of a resource. It is the verb behind photos.destroy in a resource controller, and the one people most often wire up by hand for a single "remove this" action.
When to reach for it
Deleting a record, revoking a token, detaching a relationship, cancelling a subscription. DELETE is meant to be idempotent: deleting an already-deleted resource should not be an error the second time, which is why a 404 from route model binding is usually the right response rather than a crash.
What trips people up
- Forms cannot send DELETE. Like PUT, it must be spoofed: post the form with
@method('DELETE'), which writes the hidden_methodfield. A bare link cannot delete either — an anchor issues a GET, and putting a destructive action behind GET means crawlers and prefetchers will trigger it. - Authorization, not just authentication. A DELETE route almost always needs a policy check;
->can('delete', 'photo')or an explicit$this->authorize()call in the controller. - Soft deletes. If the model uses
SoftDeletes, implicit binding will not resolve an already-trashed record; chain->withTrashed()on the route when a restore or force-delete endpoint needs it. - Return a redirect or a 204, not a rendered page.
Example
<?php
use App\Http\Controllers\PhotoController;
use Illuminate\Support\Facades\Route;
Route::delete('/photos/{photo}', [PhotoController::class, 'destroy'])
->whereNumber('photo')
->middleware('auth')
->can('delete', 'photo')
->name('photos.destroy');
echo route('photos.destroy', ['photo' => 12]);
// http://example.com/photos/12When to use it
- Remove a photo from a gallery from a confirmation form.
- Revoke an API token from a stateless api.php endpoint.
- Detach a tag from a post with a nested URI such as /posts/{post}/tags/{tag}.
- Guard destruction with a policy so only the owner can delete the record.
Frequently asked questions
Why does my delete link do nothing?
An anchor tag issues a GET request, which never matches a DELETE route. Wrap the control in a POST form containing @csrf and @method('DELETE'), or send a real DELETE with fetch/axios from JavaScript.
How do I delete a soft-deleted model from a route?
Chain ->withTrashed() onto the route definition. Implicit model binding excludes trashed rows by default and returns a 404 instead of resolving them.
Should a DELETE route return a view?
No — redirect after a browser submission so a refresh does not repeat the request, and return response()->noContent() (204) from an API endpoint.
More examples
A delete button that is really a POST form
The visible control is a submit button inside a POST form, not a link — the hidden _method field written by @method('DELETE') is what routes it to the DELETE handler. The controller authorizes before deleting and redirects afterwards, so the browser lands on a GET URL and a refresh is harmless.
<?php
// routes/web.php
Route::delete('/photos/{photo}', [PhotoController::class, 'destroy'])->name('photos.destroy');
// resources/views/photos/show.blade.php
// <form method="POST" action="{{ route('photos.destroy', $photo) }}">
// @csrf
// @method('DELETE')
// <button type="submit">Delete</button>
// </form>
// app/Http/Controllers/PhotoController.php
public function destroy(Photo $photo): RedirectResponse
{
$this->authorize('delete', $photo);
$photo->delete();
return redirect()->route('photos.index')->with('status', 'Photo deleted.');
}
echo route('photos.destroy', ['photo' => 12]);
// http://example.com/photos/12Deleting a soft-deleted model and an API variant
The first route shows why a force-delete endpoint needs ->withTrashed(): without it, implicit binding cannot find the already soft-deleted row and returns 404. The second shows an API client sending a genuine DELETE and receiving 204 No Content, with the /api prefix added automatically to the generated URL.
<?php
use Illuminate\Support\Facades\Route;
// Implicit binding skips trashed rows unless you opt in.
Route::delete('/photos/{photo}/force', [PhotoController::class, 'forceDestroy'])
->withTrashed()
->name('photos.force-destroy');
// routes/api.php — real DELETE verb, no spoofing, 204 response.
Route::delete('/tokens/{token}', function (string $token) {
request()->user()->tokens()->where('id', $token)->delete();
return response()->noContent();
})->middleware('auth:sanctum')->name('api.tokens.destroy');
echo route('photos.force-destroy', ['photo' => 12]);
echo PHP_EOL;
echo route('api.tokens.destroy', ['token' => 5]);
// http://example.com/photos/12/force
// http://example.com/api/tokens/5
Discussion