Download the PHP package salioudiabate/notify without Composer
On this page you can find all versions of the php package salioudiabate/notify. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download salioudiabate/notify
More information about salioudiabate/notify
Files in salioudiabate/notify
Package notify
Short Description A unified notification & feedback UI system for Laravel and Livewire — toasts, alerts, confirmations, dialogs, loading and progress from one fluent API. Works standalone in plain Laravel; upgrades itself automatically to real-time, no-reload notifications the moment Livewire is present.
License MIT
Homepage https://github.com/salioudiabate/notify
Informations about the package notify
Notify
A unified notification & feedback UI system for Laravel — toasts, alerts, confirmations, dialogs, loading and progress, from one fluent API. Livewire is optional. A plain Laravel controller gets the full feature set through session flashing; the moment a Livewire component opts in, the same calls become instant, no-reload pushes — nothing to rewrite either way.
Table of contents
- Why
- Installation
- Quick start
- Plain Laravel — no Livewire at all
- Livewire — instant, no-reload
- Pure client-side JS
- API
- Custom templates
- Icons, text & button colors
- Light & dark mode
- Broadcasting to a specific user
- Confirmations & server actions
- calls, validation & exceptions
- Configuration
- Security
- Roadmap
- Changelog
Why
Most notification packages pick a side: either a Livewire-only toast component, or a session-flash Blade partial with no Livewire story. Notify is built the other way around — one declarative payload, one front-end store, four ways to feed it:
| Producer | When it's used | Round trip |
|---|---|---|
| Session flash | Any controller, form request, queued job, or plain Blade page — Livewire installed or not | Next full page load |
| Livewire dispatch | A component using InteractsWithNotifications |
Instant, no reload |
window.Notify |
Any inline script, with zero backend involved | None |
Broadcast (->toUser()/->toChannel()) |
A specific user, from outside the current request entirely (a queued job, a console command) | Real-time, over Echo |
The backend never renders HTML — it only ever produces a small JSON payload (type, variant, title, message, actions, ...). resources/js/notify.js is the only thing that turns that into a toast, an alert, a confirmation dialog or a progress card, so every surface — session-flashed, Livewire-pushed, or JS-triggered — renders through the exact same visual system.
Installation
Requires PHP 8.3+, Laravel 11/12/13, and — only if you want the instant, no-reload upgrade — Livewire 3 or 4 (entirely optional, see Why).
Add the root component once, in your main layout, right before </body>:
That's it — the JS/CSS are served directly by the package (no build step, no npm install). If you'd rather bundle them through your own pipeline (Vite, etc.), publish them and set assets.serve to false in config/notify.php:
Quick start
Plain Laravel — no Livewire at all
Works from anywhere synchronous in the request lifecycle — controllers, form requests, queued jobs before a redirect, console commands (where it's a documented no-op: there's no browser to render into).
Livewire — instant, no-reload
InteractsWithNotifications registers the component the moment it boots (bootInteractsWithNotifications() — a standard Livewire trait hook, nothing package-specific), so even a plain Notify::success(...) call made anywhere during that request — a service class, a job dispatched synchronously, not just $this->notify() — is upgraded to an instant push automatically for the rest of the request.
notify()/confirm() above are one-liners that send right away — they can't reach ->danger(), ->group(), ->confirmColor(), or anything else that needs chaining before ->send(). For that, use their builder-returning counterparts instead, still bound to the same component:
Pure client-side JS
No backend call at all:
API
Shared fluent methods on every builder: title(), message(), icon(), duration(), position(), dismissible(), persistent(), group(), id(), action($label, $target, $style, $color), url($url, $label, $color), template($name), toUser($user)/toChannel($channel) (see below).
position() accepts top-right (default), top-left, top-center, bottom-right, bottom-left, bottom-center. action()'s $target resolves exactly like onConfirm() does (see below) — a route name, a URL, a Livewire method name, or a Closure all work the same way on any toast/alert/dialog action, not just a confirmation's.
Why asSuccess(), not success()? Every toast()/alert()/dialog()/progress() builder above uses asSuccess()/asError()/asWarning()/asInfo() — deliberately spelled differently from Notify::success($message), $pending->success($message), and Notify::update($id)->success($message). Those three all take a message and either send right away or need one more ->send(); a builder's asSuccess() only flips its color and takes nothing at all. Keeping the two families visually distinct means the method name alone tells you which behavior you're getting, instead of having to remember which class you're chaining off of.
Extending a builder. None of the builders are final, and all of them inherit Laravel's Macroable trait — add your own fluent methods either by subclassing, or without subclassing at all:
Custom templates
The built-in look ("default") is one entry in a template registry, not a hardcoded renderer — the backend only ever produces a payload (type, variant, title, message, actions, ...), and notify.js picks a template function to turn it into a visual. You can register your own and either use it for one notification or make it the default for the whole app, without touching the package.
h gives your template function everything the built-in ones use internally, so you don't have to reinvent action resolution or escaping:
| Helper | What it does |
|---|---|
h.el(tag, className?, innerHTML?) |
Create an element |
h.escapeHtml(value) |
HTML-escape a string |
h.icon(payload) |
Resolve the semantic icon markup for a variant |
h.actions(payload) |
Build the action-button row, already wired to h.runAction |
h.actionColor(button, color) |
Apply a per-button color override (string or {bg,fg,border}) to an element |
h.runAction(action, payload) |
Execute one action's target (url/route/Livewire/callback) and dismiss |
h.dismiss(id) |
Remove a notification by id |
Use it for one notification:
...or make it the default for every notification in the app, in config/notify.php:
A dialog template handles both confirm() and dialog() payloads — it only needs to return the box itself; the backdrop, Esc-to-close, focus and queueing when a second dialog is requested while one is already open all stay in the package, since that's shared interaction plumbing rather than something a design should have to reimplement.
Icons, text & button colors
Registering a whole template is the nuclear option — for smaller changes, icons, every piece of built-in UI chrome text, and button colors are all customizable on their own, without writing a single template function.
Icons. ->icon('name') looks up a registry of built-in icons (success, error, warning, info, neutral, trash, close). Override one, several, or add new ones, globally:
A one-off icon that isn't worth registering anywhere doesn't need either of those — pass raw markup straight to the call:
Text. Every hardcoded piece of UI chrome — the close button's label, the "N more" overflow pill, the dialog's Esc to close hint, and confirm()/cancel()'s default button labels — is not part of any payload's own free-form title/message/action text (already customizable per call); it lives in one place so a translated or reworded app only has to set it once:
ConfirmBuilder's default ->confirmText()/->cancelText() and ->url()'s default label read from these same config('notify.strings.*') keys server-side, so a single config change relabels both the PHP-built payloads and the pure-JS Notify.confirm() path consistently.
Button colors. Every button style (primary, secondary, danger, ghost, link — the same names ->action()'s third argument accepts) has its own color, decoupled from card text/icon colors, so changing one never affects the other. Override one or more styles globally:
Each style accepts bg (required to have any effect), plus optional fg (text) and border. Leave a style out to keep its built-in look.
A single button can go its own way regardless of the global setting — every button-producing method accepts an optional color, a plain string (background only) or a ['bg' => ..., 'fg' => ..., 'border' => ...] array:
Light & dark mode
Every component follows prefers-color-scheme out of the box — no setup required. Force one globally, or let visitors flip it themselves:
setColorScheme() persists the choice in localStorage, so it survives reloads without any server round-trip, and overrides both config('notify.color_scheme') and the OS preference until 'system' is passed again. If your app already has its own dark-mode cookie/session value, call Notify.setColorScheme() once on page load with that value instead of leaving it to config() — the two are independent, Notify doesn't read your app's own dark-mode flag automatically.
Broadcasting to a specific user
Session flash and Livewire dispatch both only ever target "whoever is making this request" — neither can reach a user from outside the request that concerns them at all: a queued job finishing an export, a webhook, a console command, a different HTTP request than the one the recipient is sitting on. ->toUser($user)/->toChannel($channel) are the fourth producer, for exactly that case:
This is real-time delivery to a currently-connected recipient only — there's no persistence for someone who's offline right now (that's the separate, still-open "database-backed persistent notifications" roadmap item). Three things need to already be true for it to reach anyone at all:
- The host app's own broadcasting is configured — a driver (Reverb, Pusher, Ably, ...), and
window.Echoloaded client-side. Notify doesn't set any of this up; it only usesevent()and Echo's public API once they're there. config('notify.broadcast.enabled')istrue—falseby default, so a plain app with no broadcasting driver at all never tries to reach a websocket connection that doesn't exist.routes/channels.phpauthorizes the channel — private channels always require this, same as any other Laravel broadcasting:
With that in place, <x-notify::root /> auto-subscribes each visitor to their own notify.{auth()->id()} channel — nothing extra to wire up client-side for the common case:
Set channel to a plain string, or a closure receiving the current request, for full control instead — e.g. a per-team channel rather than a per-user one:
A notification sent this way still returns the same PendingNotification as any other ->send() — ->success()/->error()/->dismiss() called on it keep routing through the same channel automatically:
Note that ->toUser()/->toChannel() need the full builder form — Notify::success($message) and friends already call ->send() internally, with no window left to set a channel first, the same limitation ->group() or ->template() have on those one-liners.
Confirmations & server actions
onConfirm() accepts three kinds of targets, resolved automatically:
- A route name or URL — the confirm button submits/navigates there.
- A Livewire method name — only when the builder was created via
$this->confirm(...)inside a component; resolved to aLivewire.find(id).call(method, ...params)client-side. - A
Closure— works even outside Livewire. The closure is serialized, cached server-side under a random single-use token, and exposed behind a signed, short-lived URL (config('notify.actions.ttl'), 5 minutes by default). Nothing about its contents ever reaches the client — only the opaque token does, and it's deleted from cache the moment it's read, guarded by a lock so two near-simultaneous requests for the same token can't both slip through and run it twice.
Set notify.actions.enabled to false if you never pass raw closures (routes and Livewire methods don't need this endpoint at all).
The signature alone doesn't authenticate anyone. It only proves the URL is untampered and not expired — not who's clicking it. If that URL ever leaks (synced browser history, a corporate proxy's access log, a Referrer-Policy leak to a third-party resource embedded on the confirmation page, a shared screenshot), whoever has it can trigger the closure within its TTL. For anything sensitive or destructive, re-check authorization inside the closure itself when it runs, rather than trusting whatever was true when onConfirm() was called:
config('notify.actions.middleware') adds extra middleware to the callback route on top of signed — e.g. ['auth'], to reject an anonymous request outright before the closure even runs. Defense in depth, not a replacement for the check above: [] by default, since not every confirmation is meant to require a login (a guest-facing "unsubscribe" confirmation, for instance).
Existing ->with('success', ...) calls, validation & exceptions
<x-notify::root /> also picks up, on every request:
- Flash keys your app already sets —
redirect()->with('success', '...'), Breeze/Jetstream'sstatus,error,warning,info— mapped inconfig('notify.session_bridge.keys'). Existing controllers render as Notify toasts with zero code changes. - Validation failures, as a dismissible error alert listing every message (
config('notify.validation')) — skipped automatically on Livewire requests, since Livewire already renders its own inline field errors.
Configuration
See config/notify.php for the full reference: default position/duration per variant, max_visible before notifications collapse into a "N more" pill, the session-flash key bridge, validation/exception message policy, and the signed-action TTL/middleware/lock wait.
Security
- Signed, single-use, time-boxed, lock-guarded callback URLs for
onConfirm(Closure ...)— see above, including why the signature isn't the same thing as authentication and what to do about it. Notify::exception()never surfaces$e->getMessage()outsidelocal/testingenvironments unless you explicitly opt in viaconfig('notify.errors.local').- All rendered strings (title, message, labels) go through the front-end's own escaping — nothing user-supplied is ever injected as raw HTML. The one deliberate exception is
->icon(): passing it markup (->icon('<svg>...</svg>'), see Icons, text & button colors) inserts it verbatim, same as every built-in icon already does — treat it like any other trusted, developer-authored template string, never liketitle()/message(), which are meant for arbitrary content and always escaped. ->toUser()/->toChannel()broadcasts depend entirely on your ownroutes/channels.phpauthorization callback — the package has no way to enforce that channel access is scoped correctly. Never put anything sensitive (a code, a token, private details) directly in a notification's message on the strength of that alone.
Roadmap
Database-backed persistent notifications (so one sent via ->toUser()/->toChannel() to an offline recipient isn't simply lost — see Broadcasting to a specific user), browser (native) notifications, presets, sound, and Notify::dialog()->view()/->component() for rendering arbitrary Blade or Livewire content inside the dialog shell — today dialog() supports title/message/icon/actions only, deliberately, rather than a half-finished remote-content pipeline.
Changelog
See CHANGELOG.md for what changed in each release.
License
MIT — see LICENSE.md.
All versions of notify with dependencies
illuminate/contracts Version ^11.0|^12.0|^13.0
illuminate/support Version ^11.0|^12.0|^13.0
laravel/serializable-closure Version ^1.3|^2.0
spatie/laravel-package-tools Version ^1.16