Laravel 13: Confirm Box Before Deleting a Record (Blade and JavaScript)
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> @endforeach If 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> @endif Flash 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.
References
- Laravel 13 documentation: Form Method Spoofing
- Laravel 13 documentation: CSRF Protection
- Laravel 13 documentation: Policies
- Laravel 13 documentation: Soft Deleting
Originally published on yasiramin.dev.