To show a confirm box before deleting a record in Laravel 13, send the delete as a DELETE form and add a small JavaScript confirm() or a dialog on submit. Always keep the real protection on the server too: CSRF token, authorisation and route model binding. This guide targets Laravel 13 and PHP 8.3 or newer and uses plain Blade and JavaScript without extra libraries.
Why use a form instead of a link?
A delete link is a GET request. Browsers, crawlers and prefetchers can follow GET links, so a link that deletes data is dangerous. Use a form with @method('DELETE') and @csrf so the request is a real DELETE with a CSRF token.
Prerequisites
- Laravel 13 on PHP 8.3 or newer.
- A resource such as
Postwith a model, migration and controller.
Step 1: Register the route
// routes/web.php
use App\Http\Controllers\PostController;
Route::middleware('auth')->group(function () {
Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
Route::delete('/posts/{post}', [PostController::class, 'destroy'])->name('posts.destroy');
});Step 2: Write the controller action
<?php
namespace App\Http\Controllers;
use App\Models\Post;
class PostController extends Controller
{
public function index()
{
return view('posts.index', [
'posts' => Post::latest()->paginate(10),
]);
}
public function destroy(Post $post)
{
$this->authorize('delete', $post);
$post->delete();
return redirect()
->route('posts.index')
->with('status', 'Post deleted.');
}
}Route model binding loads the record and returns 404 when it does not exist. The authorize() call runs the policy so a user cannot delete someone else's record just by changing the id in a form.
Step 3: How do you add the confirm box in Blade?
The simplest option is the browser's built-in confirm dialog, attached with an inline onsubmit handler:
@foreach ($posts as $post)
<tr>
<td>{{ $post->title }}</td>
<td>
<form method="POST"
action="{{ route('posts.destroy', $post) }}"
onsubmit="return confirm('Delete this post? This cannot be undone.');">
@csrf
@method('DELETE')
<button type="submit">Delete</button>
</form>
</td>
</tr>
@endforeachIf the user clicks Cancel, confirm() returns false and the browser does not submit the form. Nothing reaches the server.
Step 4: Can you avoid inline JavaScript?
Yes, and it is cleaner, especially if you use a Content Security Policy. Mark the forms with a data attribute and attach one listener:
<form method="POST" action="{{ route('posts.destroy', $post) }}"
data-confirm="Delete this post? This cannot be undone.">
@csrf
@method('DELETE')
<button type="submit">Delete</button>
</form>document.addEventListener('submit', (event) => {
const message = event.target.dataset.confirm;
if (message && !window.confirm(message)) {
event.preventDefault();
}
});Put this snippet in resources/js/app.js. Every form with a data-confirm attribute now asks first, so you do not repeat JavaScript for each button.
How do you use a styled dialog instead?
The native HTML <dialog> element works in modern browsers and gives you a styled confirm without a library:
<dialog id="confirm-dialog">
<form method="dialog">
<p id="confirm-text"></p>
<button value="cancel">Cancel</button>
<button value="ok">Delete</button>
</form>
</dialog>const dialog = document.getElementById('confirm-dialog');
document.addEventListener('submit', (event) => {
const form = event.target;
if (!form.dataset.confirm || form.dataset.confirmed) return;
event.preventDefault();
document.getElementById('confirm-text').textContent = form.dataset.confirm;
dialog.returnValue = '';
dialog.showModal();
dialog.addEventListener('close', () => {
if (dialog.returnValue === 'ok') {
form.dataset.confirmed = '1';
form.submit();
}
}, { once: true });
});How do you show the status message after deleting?
The controller flashes a status value to the session. Display it once at the top of your layout so users see that the action worked.
@if (session('status'))
<div role="status" class="alert">
{{ session('status') }}
</div>
@endifFlash data is removed after the next request, so a page refresh will not show the message again.
How do you test the delete route?
Test two things: the owner can delete, and another user cannot. The second test protects you if someone removes the policy check later.
it('lets the owner delete a post', function () {
$user = User::factory()->create();
$post = Post::factory()->for($user)->create();
$this->actingAs($user)
->delete(route('posts.destroy', $post))
->assertRedirect(route('posts.index'));
$this->assertModelMissing($post);
});
it('blocks other users from deleting', function () {
$post = Post::factory()->create();
$this->actingAs(User::factory()->create())
->delete(route('posts.destroy', $post))
->assertForbidden();
$this->assertModelExists($post);
});These tests assume your PostPolicy allows only the owner to delete. Adjust the expectations to match your own rules.
Common mistakes
- Using an
<a href>link for deletion. It is unsafe because it is a GET request. - Forgetting
@method('DELETE'), which results in a 405 error. - Forgetting
@csrf, which gives a 419 page expired error. - Relying only on the confirm box. It is a user-experience feature, not security. Authorisation must be on the server.
- Deleting related files or child records manually. Use foreign key cascades or model events so data stays consistent.
How do you verify it works?
- Open the list page and click Delete. The confirm box should appear.
- Click Cancel and check that the record is still there and no request appears in the network tab.
- Click OK and confirm that you are redirected with the status message.
- Try sending a DELETE request as a different user, for example in a feature test, and check it returns 403.
FAQ
Should I use soft deletes?
Often yes. Add the SoftDeletes trait so users can restore mistakes. A confirm box does not replace a recovery option.
Why did I get a 419 error?
The CSRF token is missing or expired. Make sure the form contains @csrf and that the session has not timed out.
Can I delete with AJAX instead?
Yes. Send a fetch request with the method DELETE and the X-CSRF-TOKEN header, then remove the row from the page when the response succeeds.
How do I restrict who can delete?
Create a policy with php artisan make:policy PostPolicy --model=Post and implement the delete method.



