brian2694/laravel-toastr
brian2694/laravel-toastr integrates the toastr.js notification library into Laravel, letting you flash success, info, warning, and error messages directly from controllers or session data. It ships with helpers, publishable config, and ready-to-use Blade assets so customizable toast notifications ap...
brian2694/laravel-toastr is a thin server-side wrapper around Toastr.js. Its job is narrow: it collects notification payloads (type, message, title, per-call options) into the session, then your layout calls render() to emit the <script> block that actually fires toastr.success(...) on the next page load. It does not ship or bundle toastr.js/jQuery for you — you supply the assets.
Install and register
composer require brian2694/laravel-toastr
On Laravel 5.5+ the Brian2694\Toastr\ToastrServiceProvider and the Toastr facade are auto-discovered. On older versions add them to config/app.php.
Publish what you need
php artisan vendor:publish --provider="Brian2694\Toastr\ToastrServiceProvider"
That gives you config/toastr.php (global defaults: positionClass, timeOut, closeButton, progressBar, maxOpened, preventDuplicates, etc.) and, depending on the tag you publish, the Blade view that generates the script block.
Where to look first
config/toastr.php — this is where 90% of “why doesn’t my toast look right” questions are answered. Everything you can pass per-call has a default here.toastr::toastr) — read it once so you understand that it is just string-interpolating your payload into JS.Toastr::render() — the single call your layout needs.First working use case
In a controller:
use Brian2694\Toastr\Facades\Toastr;
public function store(Request $request)
{
// ... persist
Toastr::success('Invoice #1042 created.', 'Saved');
return redirect()->route('invoices.index');
}
In your main layout, after jQuery and toastr.js are loaded:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
{!! Toastr::render() !!}
If the success toast appears after the redirect, your wiring is correct. Note the message is deferred via session flash — it renders on the next full page load, not the current response.
Facade vs. helper vs. DI. Three equivalent entry points exist in most versions: the Toastr facade, the toastr() helper function, and constructor-injecting the underlying Toastr class. Pick one per codebase — mixing them is the most common source of confusion in code review. The facade reads well in controllers; the helper is convenient inside closures (e.g. DB::transaction(function () { ... })) where you don’t want a use statement.
The four levels map 1:1 to toastr.js. Toastr::success(), ::info(), ::warning(), ::error() — signature is ($message, $title = '', array $options = []). If your UX needs a “neutral” toast, use info; there is no separate generic method worth bothering with.
Per-call option overrides are the workhorse pattern. Global config gives you consistency; the third argument gives you escape hatches without rewriting the config:
Toastr::error('Payment declined by gateway.', 'Stripe', [
'timeOut' => 0, // sticky, requires manual close
'closeButton' => true,
'positionClass'=> 'toast-bottom-right',
'progressBar' => false,
]);
Use sticky toasts for errors that the user must acknowledge, and short timeouts for the routine “Saved” confirmations. This is the single most valuable habit with this package: severity → timeout (success 3s, info 4s, warning 6s, error sticky).
Stacking multiple toasts in one request. Call the facade multiple times in the same request; the package queues them in the session and render() emits one script block with several calls. Order is insertion order (or reversed, depending on newestOnTop). Cap the queue with maxOpened/maxItems in config so a validation loop that flashes twenty messages doesn’t white-out the screen.
Pairing with validation and redirects. The natural flow is validate() → catch ValidationException → the default Laravel redirect. The package isn’t in that path at all, so either (a) keep validation in the standard error bag and reserve toasts for non-field errors, or (b) override failedValidation() in your FormRequest to also flash a Toastr::error. Pattern (b) is popular but duplicates information the user can already see inline — most mature apps settle on toasts for outcomes (created, deleted, failed job) and inline errors for fields.
Reusable layer for teams. In codebases with more than a couple of developers, wrap the facade in a domain-level service rather than calling Toastr:: from every controller:
class Notifier
{
public function invoiceCreated(Invoice $i): void
{
Toastr::success("Invoice #{$i->number} created.", 'Billing', ['timeOut' => 3000]);
}
}
This gives you one place to enforce tone-of-voice, timeouts, and translation (__()), and it makes the calls greppable and testable.
AJAX / SPA / Livewire. The package is fundamentally session-flash based, so it only works if the response is a redirect or a full page reload. If you submit a form via fetch and JSON-back, Toastr::render() on your layout never re-executes. Options:
return response()->json(['toast' => Toastr::render()]); then eval/inject client-side — works but fragile.toastr.success(...) directly in JS from the JSON payload you already control.window.addEventListener call toastr directly; do not fight the session flash.Integration tip: keep both mechanisms — server-rendered toasts for classic form posts, direct JS calls for anything async. The package is worth it for the former and dead weight for the latter.
Assets are your responsibility. The README is explicit about this but people skim it: no toastr.js loaded → render() throws toastr is not defined in the console and nothing appears. Also confirm load order: jQuery → toastr.js → Toastr::render(). Putting render() in <head> before the script tags is a classic silent failure.
render() must be in the layout that the redirect lands on. Flash a toast, redirect to an error page, or to a view that uses a different layout (login, PDF, print), and the toast is silently lost. If you redirect to a non-standard layout, add {!! Toastr::render() !!} there too.
Flash lifecycle. Notifications live for exactly one subsequent request. Toastr::render() doesn’t necessarily consume them in every version — check the source; in some releases a page that renders twice (nested layouts, @include in a loop) will emit duplicates. If you see the same toast twice, Toastr::clear() before re-rendering, or check whether the underlying session key is being re-flashed.
Config caching bites. Edited config/toastr.php and nothing changed? php artisan config:clear. In production with config:cache, only values read at runtime are frozen in — which is fine here, but the deploy pipeline must re-run config:cache after any config change.
Type coercion in options. Toastr.js is loose about types (timeOut: "0" vs 0, true vs "true"), and PHP will happily pass both. Prefer native types in your config array: 'timeOut' => 5000, 'closeButton' => true. Strings that look truthy but shouldn’t be (e.g. 'preventDuplicates' => 'false') will be treated as truthy in JS. If a boolean option behaves inverted, inspect the JSON in the rendered script block first.
XSS: the output is raw JS, not escaped HTML. Messages and titles are interpolated into a <script> block. Anything user-derived (a customer’s name, a filename, a search query echoed in a toast) is an injection vector unless you escape it. Use e() on user input before passing it in, and remember toastr’s escapeHtml option is a client-side switch — it isn’t on by default and doesn’t cover the title in every version. This is the most important security note in this assessment. Same applies under a strict CSP: inline <script> blocks need a nonce, and {!! ... !!} will not carry one — you’d have to publish the view and add it yourself.
Publish the view to customize. The extension point when you need nonce support, a different wrapper, a global toastr.options = {...} preamble, or a custom container id, is vendor:publish on the views tag then editing resources/views/vendor/toastr/. Don’t fork the package.
Testing. Don’t assert on rendered HTML in feature tests; assert on the session, or mock the facade:
Toastr::shouldReceive('success')->once();
// or
$this->assertTrue(session()->has('toastr::notifications'));
Mocking is cleaner because it decouples your test from the session key naming, which is an internal detail. Combine with $this->followingRedirects() for an integration assertion that the script actually appears in the response body.
Version pinning. The wrapper’s option pass-through is coupled to toastr.js 2.x semantics. If you upgrade your frontend asset build (or pull toastr 3.x via npm), verify the option names in config/toastr.php still exist — positionClass, maxOpened, and preventDuplicates are all toastr 2.x-isms, and silent no-ops are the failure mode. For that reason, pin toastr.js in package.json alongside the composer constraint.
When not to use it. If your app is Inertia/React/Vue, or your notification needs are all async, the session-flash indirection is a tax with no benefit — call toastr from your JS bundle. The package earns its keep in classic server-rendered Blade apps where a redirect()->back() after a POST needs to say “Saved.”
How can I help you explore Laravel packages today?