devmarketer/laraflash
LaraFlash is a Laravel package for adding stylish flash notifications to your app. It offers a simple API for session-based messages, multiple notification types, custom styling, and Blade-ready output so you can display success, error, or info alerts with minimal setup.
Install
composer require devmarketer/laraflash
On Laravel 5.5+ the service provider is auto-discovered. On older apps register DevMarketer\LaraFlash\LaraFlashServiceProvider in config/app.php and add the LaraFlash facade alias (exact class paths are in the package README — verify against vendor/devmarketer/laraflash/src).
Publish the bits you plan to customise
php artisan vendor:publish --provider="DevMarketer\LaraFlash\LaraFlashServiceProvider"
This gives you the config (default view/type mappings) and the Blade template under resources/views/vendor/laraflash. You rarely need both — publish views only if the stock markup doesn't match your design system.
First use case — redirect-after-write
The canonical workflow is: do work in a controller, queue a flash, redirect, render once.
use DevMarketer\LaraFlash\LaraFlash;
public function store(StorePost $request)
{
$post = Post::create($request->validated());
LaraFlash::success('Post created successfully.')
->title('Saved')
->delay(4000);
return redirect()->route('posts.show', $post);
}
Then render the bag in your layout (or use the shipped partial):
@include('laraflash::notifications')
Or roll your own:
@foreach (LaraFlash::all() as $flash)
<div class="alert alert-{{ $flash->type ?? 'info' }}">
@if ($flash->title)<strong>{{ $flash->title }}</strong>@endif
{{ $flash->message }}
</div>
@endforeach
If that renders nothing, the two things to check first are: (1) the session driver is not array in the current environment, and (2) whether your version consumes the bag on all() — call LaraFlash::clear() explicitly after rendering if messages persist across requests.
Fluent notification builder. The add() factory returns a notification object you can chain on; the type shortcuts (success, info, warning, danger) are sugar over the same thing.
LaraFlash::add('Payment failed')
->type('danger')
->title('Stripe error')
->priority(100); // higher = surfaced first
Prefer the shortcuts for the common types and the fluent form only when you need a custom type string or non-default priority.
Helper vs facade. laraflash() and the LaraFlash facade hit the same singleton. In service classes and jobs, inject the underlying class or use the helper rather than the facade so tests can swap it. Avoid calling the facade from model observers — flash state belongs to the request lifecycle, and observer methods can fire from queued contexts where there's no session.
A reusable controller trait. Day-to-day you'll want one place to standardise messages:
trait FlashesMessages
{
protected function flashSuccess(string $message, ?string $title = null): void
{
LaraFlash::success($message)->title($title ?? 'Success');
}
protected function flashError(string $message): void
{
LaraFlash::danger($message)->priority(99);
}
}
This keeps type strings and priorities consistent and makes it trivial to migrate to spatie/laravel-flash or plain session()->flash() later.
Type → CSS class mapping. Don't hardcode Bootstrap classes in the partial if you use Tailwind or a design system. Map in the Blade template (or in a view composer) so a single array drives every alert:
@php($map = ['success' => 'green', 'danger' => 'red', 'warning' => 'amber', 'info' => 'blue'])
<div class="bg-{{ $map[$flash->type] ?? 'gray' }}-100">
Priority and ordering. When a request can emit several notifications (validation warning + partial success), explicitly set priority on anything user-critical. Without priority the order is effectively insertion order and will look inconsistent once you add async or service-layer flashes.
Auto-dismiss. delay() is metadata only — LaraFlash does not ship a JS runtime. Wire it up yourself in the layout so it degrades gracefully:
<div class="alert" data-delay="{{ $flash->delay ?? 0 }}">...</div>
<script>
document.querySelectorAll('[data-delay]').forEach(el => {
const d = +el.dataset.delay;
if (d > 0) setTimeout(() => el.remove(), d);
});
</script>
AJAX / Inertia / API responses. For non-Blade consumers, serialise the bag and let the frontend toast library handle it:
return response()->json([
'data' => $post,
'flashes' => LaraFlash::all()->map->toArray(),
]);
Call LaraFlash::clear() before returning if the same request may also render a Blade view — otherwise the notification shows twice.
Testing. Flash assertions are just session assertions, which is a good reason to keep your trait thin:
$response->assertSessionHas('laraflash'); // adjust key to the package's bag name
$this->assertCount(1, LaraFlash::all()); // call before clear() runs
Use $this->withSession([]) or run the test through a full request so the session is booted; calling the facade in a bare unit test without a booted app will fail on the session store.
Version compatibility is the big one. This package predates Laravel 8+, and its illuminate/* constraint in composer.json will block installation on modern Laravel. Check the constraint before committing to it; if you're on Laravel 9/10/11 you will likely need to fork it, loosen the constraint, and verify the service provider still resolves. At 64 stars and low activity, treat it as vendored code you now own.
The bag is session-backed, so environment matters. SESSION_DRIVER=array in phpunit.xml means flashes vanish between requests — that's fine for assertions within a single request, misleading if you're testing the redirect flow. Also be aware that flashes queued inside a queued job or console command have no session and will silently no-op or throw depending on the version.
Consumption semantics vary by version. Some releases clear the bag when you call all(); others require clear(). If you see a notification on every page load, you're in the latter camp — add an explicit LaraFlash::clear() at the end of the partial, or better, wrap the whole render in a Blade component that clears in its booted/render path so you can't forget it.
Publishing views creates a stale-copy problem. Once resources/views/vendor/laraflash exists, package updates to the default template are ignored. If you only want to change markup, prefer rendering your own loop in the layout and leave the vendor view unpublished — fewer files to reconcile on upgrade.
Config caching. php artisan config:cache freezes your laraflash.php defaults. Any per-environment tweak (default type, view name) needs a config:clear during deploys, and any config value read at provider-registration time (rather than at call time) will not pick up runtime overrides.
Escaping and HTML in titles. Messages go through {{ }} by default, which is correct. If you deliberately allow markup in titles, that's an XSS surface — only use {!! !!} for content you constructed server-side, never for user input echoed from validation errors.
Namespace collisions. The LaraFlash alias is generic. If another package registers the same alias, Laravel's alias loader will pick whichever provider boots last. Rename the alias in config/app.php if you're pulling in a second flash package during a migration.
Escape hatch. Everything here is a thin layer over session()->flash(). If you hit a wall — or the composer constraint — spatie/laravel-flash covers the same ground, and a 15-line custom FlashBag service plus a Blade partial reproduces 90% of LaraFlash's value with zero dependency risk. Design your trait so that swap is a single file change.
How can I help you explore Laravel packages today?