Custom Events and EventTarget: Decoupled Communication in WordPress Plugins

Custom Events and the EventTarget interface let different parts of a WordPress plugin communicate without tight coupling — no global variables, no jQuery triggers, just the native browser event system.

Problem: Plugin components need to communicate with each other — for example, a cart update triggering a sidebar refresh — but direct function calls create tight coupling and make components hard to test or replace independently.

Solution: Use the browser's native CustomEvent API to dispatch and listen for domain-specific events on document or a shared element. This decouples the sender from the receiver: any number of listeners can react to the same event without the dispatcher knowing they exist.

The examples below dispatch a typed CustomEvent from a block editor plugin, listen for it in a separate sidebar panel, and show how to create a standalone event bus using a bare EventTarget instance.

// dispatch-event.js — fired inside a Gutenberg block's edit() function
function notifyCartUpdate( productId, qty ) {
    const event = new CustomEvent( 'myplugin:cartUpdate', {
        bubbles: true,       // propagates up the DOM tree
        composed: true,      // crosses shadow DOM boundaries
        detail: { productId, qty, timestamp: Date.now() },
    } );
    document.dispatchEvent( event );
}

// listen-event.js — loaded by a separate sidebar panel script
document.addEventListener( 'myplugin:cartUpdate', ( e ) => {
    const { productId, qty } = e.detail;
    console.log( `Product ${ productId } qty changed to ${ qty }` );
    updateSidebarBadge( qty );
} );

// Always namespace your events to avoid collisions with WP core or other plugins
// BAD:  new CustomEvent('update')
// GOOD: new CustomEvent('myplugin:cartUpdate')

Create a reusable event bus with a standalone EventTarget — no DOM required:

// event-bus.js — shared singleton module
export const bus = new EventTarget();

// publisher.js
import { bus } from './event-bus.js';
export function publishSaveComplete( postId ) {
    bus.dispatchEvent( new CustomEvent( 'saveComplete', { detail: { postId } } ) );
}

// subscriber.js
import { bus } from './event-bus.js';
bus.addEventListener( 'saveComplete', ( e ) => {
    showNotification( `Post ${ e.detail.postId } saved!` );
} );

// Remove listener when the component unmounts (important in React/Gutenberg):
function handleSave( e ) { /* ... */ }
bus.addEventListener( 'saveComplete', handleSave );
// cleanup:
bus.removeEventListener( 'saveComplete', handleSave );

NOTE: Prefer CustomEvent over jQuery's $.trigger() in new code — it works in all modern browsers, requires no library, and integrates naturally with wp.hooks and the block editor's data layer.

Leave Comment

Your email address will not be published. Required fields are marked *