slim/flash
slim/flash adds flash messaging to Slim Framework applications, storing short-lived messages in the session so they survive a redirect and display once on the next request. It offers a simple PSR-7-friendly API, middleware integration, and multiple message levels.
Install with Composer and pin the version — the features below landed in 0.4.0:
composer require slim/flash:^0.4
This is a Slim 3 package. Before anything else you need a live PHP session, because Slim\Flash\Messages reads and writes $_SESSION directly (default key slimFlash). Start the session during bootstrap, before the container resolves the flash service:
session_start();
$app = new \Slim\App();
$container = $app->getContainer();
$container['flash'] = function () {
return new \Slim\Flash\Messages();
};
Then use $this->flash inside your route callables (Slim 3 auto-binds Route to the container, so $this->flash resolves the service).
The first use case you will hit is Post/Redirect/Get. In the POST handler, add a message and redirect; in the GET handler, read it back:
$app->post('/contact', function ($request, $response) {
// ... validate / persist
$this->flash->addMessage('success', 'Thanks, we got your message.');
return $response->withRedirect('/contact');
});
$app->get('/contact', function ($request, $response) {
return $this->view->render($response, 'contact.twig', [
'flash' => $this->flash->getMessages(),
]);
});
Look at Slim\Flash\Messages first — it is the whole package. The relevant surface is now addMessage(), getMessage(), getMessages(), getFirstMessage(), hasMessage(), clearMessage(), and clearMessages().
PRG flash in a middleware-friendly way. Keep the container registration (not $app->add(new Messages())) if you need access from both routes and views. Adding it as middleware also works — it implements Slim 3's middleware interface — but then you still need a reference to the instance for reads, so the container registration is the practical choice.
Grouped keys for UI states. Use distinct keys (success, error, info) and iterate them in your template. This maps cleanly onto Bootstrap/alert markup:
foreach ($this->flash->getMessages() as $type => $messages) {
foreach ($messages as $msg) {
// render alert$type
}
}
One-message buckets with getFirstMessage() (0.4.0). When a key holds a single message — a login error, a one-line confirmation — skip the nested loop:
$error = $this->flash->getFirstMessage('error');
// null (or your default) when nothing was flashed
This is the cleanest read for banners where only the first message matters; the per-key array form still applies if you deliberately stack several.
Clearing precisely with clearMessage() (0.4.0). clearMessages() nukes every bucket; clearMessage($key) drops a single key and leaves the rest intact. Use it when one region of the page consumes success while error must survive for another partial, or when a handler renders and re-renders the same key in one request.
Custom storage key (0.4.0). The session key is no longer hard-wired to slimFlash. Pass your own as the second constructor argument:
$container['flash'] = function () {
return new \Slim\Flash\Messages($_SESSION, 'app_flash');
};
Two reasons to reach for this: avoiding collisions with other packages that squat on slimFlash, and running two independent flash bags in the same app (e.g. admin_flash for the back office, app_flash for the storefront) so a shared session never leaks messages across contexts. Decide the key once, at construction — it is the $_SESSION subkey used for every subsequent read and write, so a second instance with a different key will never see the first one's messages.
Twig integration. Expose flash as a view variable in a global middleware, or pass it explicitly per route. If you use slim/twig-view, add it to the view's getEnvironment()->addGlobal() so partials can render alerts without touching every controller.
Reading a single message. Use hasMessage($key) then getMessage($key, $default) when you only care about one bucket (e.g. a login error) and want an explicit fallback rather than a null check.
Testing. The constructor accepts a storage argument by reference, so you can bypass $_SESSION in unit tests. Remember to assign the array to a variable first (see Gotchas):
$storage = [];
$flash = new \Slim\Flash\Messages($storage);
$flash->addMessage('test', 'hi');
$this->assertTrue($flash->hasMessage('test'));
$this->assertSame('hi', $flash->getFirstMessage('test'));
Add a case with a custom key to prove your two-bag setup stays isolated.
Integration with session middleware. If you use a Slim 3 session middleware (e.g. slim/session or a custom one), start it early enough that session_start() has run before the container factory for flash executes. The flash object binds $_SESSION by reference at construction time — if it is constructed before the session exists, it throws.
Clearing. Call clearMessages() explicitly if you render flashes in a partial and want to guarantee they aren't shown twice in the same request lifecycle; prefer clearMessage($key) when only one bucket has been consumed.
Messages without a started session raises a RuntimeException('Session not started'). Call session_start() (or your session middleware) before the container resolves flash.session_start() placement matters. Because the constructor captures $_SESSION by reference, session_start() must run during bootstrap — not lazily inside a route. Calling it after the service is constructed leaves the flash bound to an empty array.$_SESSION['slimFlash'] while the app writes app_flash (or vice versa), you will debug a phantom. Keep the key in config and use the same value everywhere the service is built.$_SESSION. Two instances with different keys coexist happily, but clearMessages() only clears its own bag — it will not wipe another instance's messages. That is usually what you want; do not assume it is a global session flush.$_SESSION. Messages survive across requests until read/cleared, so a session_regenerate_id() or session_destroy() (e.g. on logout) silently drops pending flash data. Verify flash still works after login/logout flows.clearMessages() if you re-render in the same request or you will see duplicates. getFirstMessage() does not remove the message it returns.addMessageNow behavior distinction in older docs. If you need a message on the current request, do not rely on flash; assign it directly to the view instead.composer.json and treat it as frozen. It generally runs fine on PHP 8, but you own any compatibility fixes.new Messages($storage) requires $storage to be a variable; you cannot pass an array literal because the constructor parameter is by-reference. This trips up tests — and is easy to get wrong when you add the new second argument.$_SESSION['slimFlash'] (or your custom key) directly when messages "disappear." The most common causes are a missing session_start(), a mismatched storage key between two service definitions, the session being regenerated mid-flow, or getMessages() being called before addMessage() in the request order.How can I help you explore Laravel packages today?