Skip to content

Channels

Jonathan Bossenger edited this page Nov 10, 2026 · 1 revision

Channels

A channel is a named topic that notifications are sent on, for example "plugin updates" or "new comments". Channels are what the plugin is organised around. The channel registry, the notifications_messages table and the notifications_subscriptions table are all keyed on the channel name. See Database Schema.

Status: Registering and listing channels works on the develop branch. Sending notifications on a channel, choosing who receives them, and managing subscriptions are planned for 0.3.0 and are not built yet. Each section below is marked Built or Planned. The planned design comes from the 0.3.0 release plan.

Terminology: The older Requirements (v1) page uses "channel" to mean the delivery method (admin bar, email, mobile app). In the current design a channel is the topic of a notification. Where a notification is shown is set by its context, for example adminbar.

How it fits together

 Plugin / core                Channel registry             Database
+-----------------+          +------------------+
| register_channel|--------->| my-plugin/foo    |
+-----------------+          | core/updates ... |
                             +------------------+
+-----------------+    look up       |
| notify(channel) |------------------+
+-----------------+                  |
         | resolve recipients        v
         |  (explicit, or channel subscribers   +------------------------+
         |   minus snoozed)                     | notifications_messages |  one row per notification
         +------------------------------------->| notifications_queue    |  one row per recipient
                                                +------------------------+
                                                            |
                              REST API  <-------------------+
                    (admin bar, filtered by channel_name, context)

Registering a channel (Built)

Register channels on the init hook with WP\Notifications\register_channel(), defined in includes/channels.php:

add_action( 'init', function () {
	WP\Notifications\register_channel(
		'my-plugin/order-shipped',
		array(
			'title'       => __( 'Order Shipped', 'my-plugin' ),
			'description' => __( 'Order shipping events.', 'my-plugin' ),
			'icon'        => 'cart',
			'context'     => 'adminbar',
		)
	);
} );
Argument Required Description
title Yes Human-readable title of the channel, shown to users.
description No Description of the events the channel covers.
icon No Icon for the channel.
context No Where notifications from this channel are displayed.

register_channel() returns the WP\Notifications\Model\Channel object, or false on failure. You can also pass a Model\Channel instance instead of a name and arguments.

To remove a channel, call WP\Notifications\unregister_channel( 'my-plugin/order-shipped' ). It returns the removed channel, or false if that name wasn't registered.

Naming rules

Channel names must:

  • be strings
  • contain no uppercase characters
  • match namespace/name, using only lowercase letters, digits and hyphens (/^[a-z0-9-]+\/[a-z0-9-]+$/)
  • not already be registered

A name that breaks these rules triggers _doing_it_wrong(), and registration returns false.

Use your plugin or theme slug as the namespace. The core namespace is reserved for channels the plugin registers itself.

The registry

WP\Notifications\Channel_Registry (includes/class-channel-registry.php) is a single shared instance, like the block type registry. It lives only in memory for the current request, so channels must be registered on every request and are never written to the database. The database stores only the channel name.

Method Description
Channel_Registry::get_instance() Get the registry.
register( $name, $args ) Register a channel.
unregister( $name ) Unregister a channel.
get_registered( $name ) Get one channel, or null if it isn't registered.
get_all_registered() Get all registered channels, keyed by name.
is_registered( $name ) Check whether a channel is registered.

Core channels (Built)

The plugin registers these channels on init. Nothing sends notifications on them yet, because nothing is hooked to the matching WordPress events.

Channel Title
core/updates WordPress Updates
core/plugin-install Plugin Install
core/plugin-uninstall Plugin Uninstall
core/plugin-activate Plugin Activate
core/plugin-deactivate Plugin Deactivate
core/plugin-updates Plugin Update
core/post-new New Post
core/post-edit Edit Post
core/post-delete Delete Post
core/comment-new New Comment

Listing channels over REST (Built)

GET /wp-notifications/v1/channels returns every registered channel. Each item has name, title, description, icon and context. It is meant to feed a settings screen where users choose which channels they want to hear about.

The endpoint does not filter channels by permission yet.

Sending a notification on a channel (Planned)

A new notify() function will send a notification on a channel, with $channel->emit() as a shorthand that calls it:

WP\Notifications\notify(
	'my-plugin/order-shipped',
	array(
		'title'    => __( 'Order #123 shipped', 'my-plugin' ),
		'message'  => __( 'The order is on its way.', 'my-plugin' ),
		'severity' => 'info',
	)
); // Returns the message ID, or a WP_Error.

The planned flow:

  1. Look the channel up in the registry. If it isn't registered, return a WP_Error. This guarantees that only registered channel names reach the channel_name column.
  2. Require a non-empty message, and build the message.
  3. Work out who receives it (see below).
  4. Save one row to notifications_messages and one row per recipient to notifications_queue.
  5. Fire the wp_notifications_notified action.

notify() will not check the caller's capabilities. Code running inside WordPress already has full database access, so permissions are checked at the REST API boundary instead.

Who receives a notification (Planned)

Recipients are resolved in this order:

  1. If the notify() call gives explicit recipients (user IDs and/or role slugs), use them.
  2. Otherwise, use the users subscribed to the channel, leaving out anyone whose snoozed_until is still in the future.
  3. MVP rule: if the channel has no subscribers, every user with the read_notifications capability receives it.
  4. Finally, the wp_notifications_recipients filter can change the list.

Because of rule 3, in 0.3.0 subscriptions can only snooze a channel. Users receive notifications without subscribing first. Changing this later so that no subscribers means no recipients would break existing behaviour.

Subscriptions (Planned)

A subscription is a row in notifications_subscriptions that links a user to a channel by user_id and channel_name, with an optional snoozed_until time.

The planned REST routes are:

Route Methods Purpose
/wp-notifications/v1/subscriptions GET List the current user's subscriptions.
/wp-notifications/v1/subscriptions/{namespace}/{name} GET, PUT/PATCH, DELETE Read, subscribe to or snooze, and unsubscribe from one channel.

Today GET /wp-notifications/v1/subscriptions returns an empty list.

Displaying notifications (Planned)

Each notification has a context (default adminbar) that says where it appears. When listing notifications over REST, clients will be able to filter by channel_name and context. The channel_name column is indexed for this.

Known issues

  • Channel titles are copied into every message. Each message row stores the channel title twice: in the channel_title column and in the meta JSON. If a plugin later changes a channel's title, older messages keep the old one. The 0.3.0 plan drops this copy (#372).
  • Name length isn't limited. The channel_name column is VARCHAR(50), but registration doesn't check name length. A longer name passes registration and then fails or gets cut off when saved. The 0.3.0 plan widens the column to VARCHAR(64) in both tables. A matching length check in the registry would close the gap.
  • Subscription permissions. The permission check on the subscriptions REST endpoint is commented out, so logged-out users can call it. This is harmless today only because the endpoint returns nothing.

Clone this wiki locally