Laravel 13: Confirm Box Before Deleting a Record (Blade and JavaScript)

2026-11-13来源 dev.toEnglish

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 Post with 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?

  1. Open the list page and click Delete. The confirm box should appear.
  2. Click Cancel and check that the record is still there and no request appears in the network tab.
  3. Click OK and confirm that you are redirected with the status message.
  4. 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

Originally published on yasiramin.dev.

原文:https://www.a8q9.cn/en/article/laravel-13-confirm-box-before-deleting-a-record-blade-and-javascript-74cad4.html

相关文章