Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Livewire Swal Laravel Package

tuhin-su/livewire-swal

Reusable SweetAlert2 integration for Laravel 10/11+ and Livewire 3, with zero-config CDN injection and one-line PHP helpers for toasts, modals, confirmations, input and password prompts, plus server-verified and cryptographically signed secure actions.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install

composer require tuhin-su/livewire-swal

That's the whole setup for the default path. The package ships a middleware that injects the SweetAlert2 CDN <script> tag and its JS event wrapper before </body> on every HTML response — no npm, no Vite, no layout edits.

2. Add the trait

namespace App\Livewire;

use Livewire\Component;
use LaravelSwal\Traits\Swal;

class Dashboard extends Component
{
    use Swal;

    public function save()
    {
        // ... persist
        $this->swalToastSuccess('Saved', 'Record updated.');
    }
}

3. Where to look first

  • README.md — helper signatures are all there; the "Secure Actions" section is the part worth reading carefully.
  • config/laravel-swal.php after php artisan vendor:publish --tag=laravel-swal-config — the only knobs that matter are auto_inject and swal_cdn.
  • resources/js/vendor/laravel-swal/swal.js (if you publish assets) — this is the actual event bridge between PHP dispatches and Swal.fire().

First real use case: replace your session()->flash('message', ...) success banners with $this->swalToastSuccess(...) at the end of a form submit, and replace any confirm('Delete?') JS with $this->swalConfirm(...). That's a 10-minute win.


Implementation Patterns

Toast-after-save (the bread and butter)

public function save()
{
    $this->validate();
    $this->post->save();
    $this->swalToastSuccess('Saved', 'Post updated.', ['timer' => 3000]);
}

Everything accepts a third $opts array that is passed straight through to SweetAlert2's config object, so anything in the SWAL2 docs is available.

Confirm → event listener

The pattern that matters for destructive UI: you fire a confirm from PHP, and the actual work happens in an #[On] listener that only runs if the user clicked "Confirm".

// Trigger
public function askDelete(int $id)
{
    $this->swalConfirm(
        title: 'Delete post?',
        text: 'This cannot be undone.',
        opts: ['confirmButtonText' => 'Delete'],
        thenEvent: 'posts.delete',
        thenParams: ['id' => $id],
    );
}

// Handler (separate method, same component)
use Livewire\Attributes\On;

#[On('posts.delete')]
public function delete(array $payload)
{
    Post::find($payload['id'])->delete();
    $this->swalToastSuccess('Deleted', 'Post removed.');
}

Use $thenEventTo (a component name/ID) when the listener lives on a different Livewire component — handy for a global "delete row" button on an index page whose listener is on a parent component.

Input → event listener

$this->swalTakeInput(
    title: 'Add note',
    input: 'textarea',
    thenEvent: 'notes.save',
);

#[On('notes.save')]
public function storeNote(array $payload)
{
    $this->note = trim($payload['value'] ?? '');
    // ...
}

Same shape for swalPromptPassword — it's just swalTakeInput with input: 'password' preset.

Password re-verification

For "confirm your password before X" flows, swalVerifyCurrentUserPassword is the shortest path — it runs Hash::check server-side, toasts the result automatically, and dispatches your $thenEventTrue or $thenEventFalse:

$this->swalVerifyCurrentUserPassword(
    thenEventTrue: 'settings.unlocked',
    thenEventFalse: 'settings.locked',
);

Secure actions (the important one)

Regular swalConfirm flows are bypassable — a user can open the console and Livewire.dispatch('posts.delete', {id: 5}) directly. swalSecureAction closes that hole by encrypting the method name and params with APP_KEY, binding the payload to the session and component class, and calling a protected method server-side after the multi-step prompt succeeds.

public function initiateDelete(int $id)
{
    $this->swalSecureAction(
        method: 'deleteUser',
        params: ['id' => $id],
        requirePassword: true,
        confirmTitle: 'Delete user?',
        confirmText: 'Irreversible.',
    );
}

protected function deleteUser(int $id)
{
    User::findOrFail($id)->delete();
    $this->swalToastSuccess('Deleted');
}

The key discipline: the target method must be protected or private. Livewire only exposes public methods, so a protected target cannot be called from devtools under any circumstances. If you make it public "just to test it", you've undone the entire security model.

Triggering from JS / Alpine

When you need a button that isn't wired to a Livewire action (or you want to fire from Alpine state), pre-generate the payload in render():

public function render()
{
    return view('livewire.settings', [
        'deletePayload' => $this->swalGenerateSecureActionPayload('deleteAccount', ['id' => 5], true),
    ]);
}
<button onclick="window.swalExecuteSecureAction('{{ $deletePayload }}', true, {
    confirmTitle: 'Delete account?'
})">Delete</button>

All helpers are also on window (SwalToast, SwalFire, SwalConfirm, etc.) so you can call them from Alpine x-on:click, vanilla JS, or a <script> in Blade.

Manual asset compilation (NPM + Vite)

If you can't use a CDN: composer require, then vendor:publish both laravel-swal-config and laravel-swal-assets, set 'auto_inject' => false, npm install sweetalert2, and in app.js:

import Swal from 'sweetalert2'
window.Swal = Swal
import './vendor/laravel-swal/swal'

Don't forget @vite([...]) in the layout or nothing loads.


Gotchas and Tips

The auto-injection middleware runs on every HTML page. That's convenient but adds ~50ms and a CDN round-trip to every response, including error pages and auth screens. In production, consider self-hosting SWAL via swal_cdn pointing at a local asset, or fully switching to the Vite path.

Never expose a secure-action target as public. swalSecureAction protects the invocation path, not the method visibility. Making deleteUser public so you can test it from a test method is a live bypass. If you need testability, extract the logic to a service and have both the protected method and your test call the service.

APP_KEY rotation invalidates all in-flight payloads. Payloads are signed with the app key, so after a deploy that rotates APP_KEY, users mid-flow will get an invalid-payload error. Expected, but surprising the first time. The payloads also carry an expiry and session ID — if the user's session regenerates (e.g. after login), the payload dies.

The confirm event payload shape is array $payload, not named args. A very common mistake is writing #[On('users.delete')] public function delete($id) and getting null forever. The listener always receives the event params wrapped in a single array; destructure with $payload['id'].

Don't nest event listeners on the same component you're already in. Fire from component A, listen in component A is fine, but if you fire from A to a listener in B via $thenEventTo: 'B', remember B is a separate instance — any state on A (like a selected row) isn't shared. Pass everything you need through $thenParams.

Timer/position options don't work on modals. Toasts accept timer and position (top-end by default); swalFire* modals ignore timer unless you set it explicitly. Don't assume defaults carry over.

For custom JS, use window.Swal in the same tick. The auto-injected script loads SWAL as a deferred CDN script. If you have a Blade <script> that runs inline and calls window.Swal.fire() before the wrapper is ready, you'll get "Swal is not defined". Either wrap in DOMContentLoaded or move your logic into an event handler that Livewire dispatches.

Password verification requires the web guard and an authenticated user. swalVerifyCurrentUserPassword calls Hash::check against auth()->user()->password. On guest routes it silently fails (or worse, throws a null-user error). Guard the call with auth()->check().

Debugging secure actions is opaque by design. When a payload is rejected (expired, session mismatch, class mismatch, signature tampered) the failure is surfaced as a generic "verification failed" toast. For local debugging, add temporary Log::debug() inside your listener and check whether the encrypted round-trip even reaches it. Nine times out of ten the bug is: wrong method: name (case-sensitive, no namespace), params mismatch with the target's signature, or the session ID changed between render and click.

Don't vendor:publish the config unless you need to. If you publish it and later update the package, your published config won't pick up new keys. Only publish when you're actively changing auto_inject or swal_cdn.

For a project with CSP: the auto-injected script is a bare <script src="https://cdn.jsdelivr.net/..."> and the wrapper is inline — you'll need to allow cdn.jsdelivr.net and 'unsafe-inline' in your script-src, or take the Vite path (which is strongly preferred with strict CSP).

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel