Route::delete

Registers a route that answers DELETE requests for removing a resource.

Signature
delete(string $uri, array|string|callable $action = null): Illuminate\Routing\Route

Parameters

ParameterTypeDescription
$$uristringThe URI pattern to match, usually ending in the resource identifier as a {param} segment.
$$actionoptionalarray|string|callable= nullA 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 _method field. 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

Example · php
<?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/12

When 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.

Example · php
<?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/12

Deleting 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.

Example · php
<?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

  • Be the first to comment on this lesson.