foxelabs / wp-flash-notices
WordPress admin notices as flash notices, stored with the transient API so they render once after a page reload, with swappable storage and rendering drivers.
Requires
- php: >=7.4
Requires (Dev)
This package is auto-updated.
Last update: 2026-08-04 18:28:26 UTC
README
WP Flash Notices is a small WordPress library that turns admin notices into flash notices: you queue a notice during one request (typically right before a redirect), and it is printed on the next page load and then cleared. It borrows the idea from framework flash messages and implements it on top of the WordPress transient API.
Notices queued during a request are held in memory and flushed to storage once, on shutdown, so queueing ten notices
still costs a single database write. Multisite network notices are kept in a separate queue, and both the storage and
the rendering layers are swappable.
📖 Full documentation: docs.foxelabs.com
Requirements
- PHP 7.4 or higher
- WordPress 6.0+
- Composer
Installation
composer require foxelabs/wp-flash-notices
The library autoloads under the FoxeLabs\Notices\ namespace via PSR-4.
Architecture
The library is organised as a tiny container wired up by the entry class FoxeLabs\Notices\Notices. The folder layout
mirrors the namespace:
src/
├── Notices.php # Container + entry point
├── Contracts/
│ ├── StoreInterface.php
│ └── RendererInterface.php
├── Storage/
│ └── TransientStore.php # (site_)transient wrapper, separate site + network queues
├── Rendering/
│ └── AdminNoticeRenderer.php # Core admin notice markup
├── Support/
│ ├── KeyPrefixer.php # Shared transient key + hook name prefixing
│ └── Notice.php # Immutable notice value object
└── Exceptions/
└── NoticeException.php
Services receive their collaborators by constructor injection so they can be unit-tested without WordPress in the loop.
Construction has no side effects — call register() to attach the hooks.
Usage
Initialisation
Each instance is scoped to a single prefix. Every transient key and every hook name is namespaced under it, so two plugins using this library on the same site never see each other's notices:
$notices = \FoxeLabs\Notices\Notices::get_instance( 'my_plugin' ); $notices->register();
Call register() once, early — plugins_loaded is a good place. You can also instantiate directly, which is what you
want when injecting your own store or renderer:
$notices = new \FoxeLabs\Notices\Notices( 'my_plugin', $my_store, $my_renderer );
Anywhere else in your plugin, Notices::get_instance( 'my_plugin' ) hands back the same container, so you don't need to
pass it around.
Adding a notice
$notices->add( $key, $message, $type = 'info', $dismissible = true, $network = false );
| Parameter | Type | Description |
|---|---|---|
$key |
string |
Unique key. Re-using a key replaces the earlier notice. |
$message |
string |
Notice body. Limited HTML allowed (run through wpautop() + wp_kses()). |
$type |
string |
success, info, warning or error. Unknown types fall back to info. |
$dismissible |
bool |
Show the dismiss button. |
$network |
bool |
Queue as a network admin notice (multisite only). |
It returns the queued Notice, and throws NoticeException when the key or message is empty.
// Notice with a green bar. $notices->add( 'settings-saved', 'Your settings have been saved.', 'success' ); // Notice with a red bar. $notices->add( 'import-failed', 'The import could not be completed.', 'error' ); // Notice with a yellow bar. $notices->add( 'license-expiring', 'Your license expires in 3 days.', 'warning' ); // Notice with a blue bar, without a dismiss button. $notices->add( 'sync-running', 'A sync is currently running.', 'info', false ); // Network admin notice (multisite). $notices->add( 'network-update', 'All sites have been updated.', 'success', true, true );
Off multisite, a notice queued with $network = true silently falls back to the regular site queue.
Reading and clearing
$notice = $notices->get( 'settings-saved' ); // ?Notice $notices->fetch(); // Notice[] keyed by notice key $notices->clear(); // Delete the stored queue
Pass true as the last argument to any of these to work on the network queue.
Notice is an immutable value object with key(), message(), type(), is_dismissible() and to_array().
Front-end notices
Notices are printed automatically on admin_notices and network_admin_notices. To print them in a front-end
template, fire the prefixed action yourself:
do_action( 'my_plugin_front_notices' );
Hooks
Every hook name is prefixed with your instance prefix — the examples below assume my_plugin.
Filters
| Hook | Arguments | Purpose |
|---|---|---|
my_plugin_flash_notice_types |
string[] $types |
Register custom notice types. A type maps to the notice-{type} CSS class. |
my_plugin_flash_notice_item |
Notice $notice, bool $network |
Replace a notice just before it is queued. Return a Notice to override. |
my_plugin_flash_notices_fetch |
Notice[] $notices, bool $network |
Modify the notices read from storage. |
my_plugin_flash_notices_auto_render |
bool $enable, bool $network |
Return false to stop the library printing notices — nothing is cleared either. |
my_plugin_flash_notices_auto_clear |
bool $enable, bool $network |
Return false to keep notices after rendering. You must then call clear() yourself. |
Actions
| Hook | Arguments |
|---|---|
my_plugin_flash_notices_after_queue |
Notice $notice, bool $network, Notice[] $queue, Notice[] $network_queue |
my_plugin_flash_notices_after_save |
Notice[] $queue, Notice[] $network_queue |
my_plugin_flash_notices_after_render |
Notice[] $notices, bool $network |
my_plugin_flash_notices_after_clear |
bool $network |
my_plugin_front_notices |
— (fire it yourself to print notices on the front end) |
Custom storage and rendering
Implement StoreInterface to change where the queue lives, or RendererInterface to change the markup, and pass your
implementation to the constructor:
use FoxeLabs\Notices\Contracts\RendererInterface; use FoxeLabs\Notices\Support\Notice; class MyRenderer implements RendererInterface { public function render( array $notices ): void { foreach ( $notices as $notice ) { printf( '<div class="my-toast my-toast--%s">%s</div>', esc_attr( $notice->type() ), esc_html( $notice->message() ) ); } } } $notices = new \FoxeLabs\Notices\Notices( 'my_plugin', null, new MyRenderer() ); $notices->register();
TransientStore also takes an expiration (default one day), so an unread queue cannot linger in the options table
forever:
use FoxeLabs\Notices\Storage\TransientStore; use FoxeLabs\Notices\Support\KeyPrefixer; $store = new TransientStore( new KeyPrefixer( 'my_plugin' ), HOUR_IN_SECONDS ); $notices = new \FoxeLabs\Notices\Notices( 'my_plugin', $store );
Upgrading from 1.x
Version 2.0.0 is a breaking rewrite. If you are coming from duckdev/wp-flash-notices:
| 1.x | 2.0.0 |
|---|---|
duckdev/wp-flash-notices |
foxelabs/wp-flash-notices |
DuckDev\WP_Flash_Notices |
FoxeLabs\Notices\Notices |
| Constructor took a transient name | Constructor takes a prefix; keys are derived from it |
| Hooks attached in the constructor | Call register() explicitly |
wp_flash_notices_* hooks |
{prefix}_flash_notices_* / {prefix}_flash_notice_* |
front_notices action |
{prefix}_front_notices |
get() / fetch() returned arrays |
Return Notice objects (null / [] when absent) |
clear_after_render() public method |
Removed; clearing is internal to render() |
| Notices stored forever | Stored with an expiration (default one day) |
Stored 1.x transients are not migrated — any notice left in the old transient at upgrade time is simply not shown.
Two 1.x bugs are fixed along the way: network notices were saved from the wrong queue (so the site queue was written to the network transient), and the auto-clear callback received mismatched arguments, so notices were not reliably cleared after rendering.
Development
composer install composer test composer phpcs
License
GPL-2.0-or-later. See LICENSE.