The @wordpress/hooks package provides a lightweight event management system for JavaScript, similar to WordPress PHP hooks. It enables extensibility through actions and filters.
Installation
npm install @wordpress/hooks --save
Core Concepts
Actions
Actions allow you to execute code at specific points without returning a value.
Filters
Filters allow you to modify data before it’s used.
Basic Usage
Global Hooks
In WordPress, hooks are available globally:
// Add an action
wp.hooks.addAction( 'myPlugin.init', 'myPlugin', () => {
console.log( 'Plugin initialized' );
} );
// Trigger the action
wp.hooks.doAction( 'myPlugin.init' );
Custom Hook Instance
import { createHooks } from '@wordpress/hooks';
const hooks = createHooks();
hooks.addAction( 'init', 'myNamespace', () => {
console.log( 'Initialized' );
} );
hooks.doAction( 'init' );
Actions
addAction
Registers a callback to be executed when an action fires.
import { addAction } from '@wordpress/hooks';
addAction(
'editor.blockInserted',
'myPlugin/logBlockInsert',
( block ) => {
console.log( 'Block inserted:', block.name );
},
10 // Priority (lower runs first)
);
Parameters:
hookName (string) - Name of the action
namespace (string) - Unique identifier (vendor/plugin/function)
callback (Function) - Function to execute
priority (number) - Optional priority (default: 10)
doAction
Triggers all callbacks registered to an action.
import { doAction } from '@wordpress/hooks';
doAction( 'editor.blockInserted', blockData, additionalInfo );
removeAction
Removes a previously registered action callback.
import { removeAction } from '@wordpress/hooks';
removeAction( 'editor.blockInserted', 'myPlugin/logBlockInsert' );
removeAllActions
Removes all callbacks for an action.
import { removeAllActions } from '@wordpress/hooks';
removeAllActions( 'editor.blockInserted' );
hasAction
Checks if any callbacks are registered for an action.
import { hasAction } from '@wordpress/hooks';
if ( hasAction( 'editor.blockInserted' ) ) {
console.log( 'Action has callbacks' );
}
// Check specific namespace
if ( hasAction( 'editor.blockInserted', 'myPlugin/logBlockInsert' ) ) {
console.log( 'Namespace has callback registered' );
}
doingAction
Checks if an action is currently being executed.
import { doingAction } from '@wordpress/hooks';
if ( doingAction( 'editor.blockInserted' ) ) {
console.log( 'Currently executing blockInserted action' );
}
didAction
Returns the number of times an action has been triggered.
import { didAction } from '@wordpress/hooks';
const count = didAction( 'editor.blockInserted' );
console.log( `Action fired ${ count } times` );
Filters
addFilter
Registers a callback to modify a value.
import { addFilter } from '@wordpress/hooks';
addFilter(
'editor.blockTitle',
'myPlugin/modifyTitle',
( title, block ) => {
return `${ title } (Modified)`;
},
10
);
applyFilters
Applies all filters and returns the modified value.
import { applyFilters } from '@wordpress/hooks';
const title = applyFilters( 'editor.blockTitle', 'Paragraph', blockData );
// Returns: "Paragraph (Modified)"
removeFilter
Removes a previously registered filter callback.
import { removeFilter } from '@wordpress/hooks';
removeFilter( 'editor.blockTitle', 'myPlugin/modifyTitle' );
removeAllFilters
Removes all callbacks for a filter.
import { removeAllFilters } from '@wordpress/hooks';
removeAllFilters( 'editor.blockTitle' );
hasFilter
Checks if any callbacks are registered for a filter.
import { hasFilter } from '@wordpress/hooks';
if ( hasFilter( 'editor.blockTitle' ) ) {
console.log( 'Filter has callbacks' );
}
doingFilter
Checks if a filter is currently being applied.
import { doingFilter } from '@wordpress/hooks';
if ( doingFilter( 'editor.blockTitle' ) ) {
console.log( 'Currently applying blockTitle filter' );
}
didFilter
Returns the number of times a filter has been applied.
import { didFilter } from '@wordpress/hooks';
const count = didFilter( 'editor.blockTitle' );
console.log( `Filter applied ${ count } times` );
Async Operations
doActionAsync
Triggers action callbacks asynchronously.
import { addAction, doActionAsync } from '@wordpress/hooks';
addAction( 'savePost', 'myPlugin/save', async () => {
await fetch( '/api/save' );
} );
await doActionAsync( 'savePost' );
applyFiltersAsync
Applies filters asynchronously.
import { addFilter, applyFiltersAsync } from '@wordpress/hooks';
addFilter( 'content', 'myPlugin/process', async ( content ) => {
const processed = await processContent( content );
return processed;
} );
const result = await applyFiltersAsync( 'content', originalContent );
Practical Examples
Plugin Initialization
import { addAction, doAction } from '@wordpress/hooks';
// Plugin A
addAction( 'app.init', 'pluginA/init', () => {
console.log( 'Plugin A initialized' );
} );
// Plugin B
addAction( 'app.init', 'pluginB/init', () => {
console.log( 'Plugin B initialized' );
} );
// Application
function initializeApp() {
doAction( 'app.init' );
}
Modifying Data
import { addFilter, applyFilters } from '@wordpress/hooks';
// Add custom validation
addFilter(
'form.validateEmail',
'myPlugin/corporateEmailOnly',
( isValid, email ) => {
if ( ! email.endsWith( '@company.com' ) ) {
return false;
}
return isValid;
}
);
// Use the filter
function validateEmail( email ) {
const basicValidation = email.includes( '@' );
return applyFilters( 'form.validateEmail', basicValidation, email );
}
Block Editor Extensions
import { addFilter } from '@wordpress/hooks';
// Modify block attributes
addFilter(
'blocks.registerBlockType',
'myPlugin/addCustomAttribute',
( settings, name ) => {
if ( name === 'core/paragraph' ) {
return {
...settings,
attributes: {
...settings.attributes,
customField: {
type: 'string',
default: '',
},
},
};
}
return settings;
}
);
Priority Management
import { addAction } from '@wordpress/hooks';
// Runs first (priority 5)
addAction( 'init', 'plugin/early', () => {
console.log( '1. Early initialization' );
}, 5 );
// Runs second (default priority 10)
addAction( 'init', 'plugin/normal', () => {
console.log( '2. Normal initialization' );
} );
// Runs last (priority 20)
addAction( 'init', 'plugin/late', () => {
console.log( '3. Late initialization' );
}, 20 );
Hook Events
The hooks system triggers meta events:
hookAdded
Fired when a hook is added.
import { addAction } from '@wordpress/hooks';
addAction( 'hookAdded', 'myPlugin/trackHooks', ( hookName, namespace ) => {
console.log( `Hook added: ${ hookName } (${ namespace })` );
} );
hookRemoved
Fired when a hook is removed.
import { addAction } from '@wordpress/hooks';
addAction( 'hookRemoved', 'myPlugin/trackHooks', ( hookName, namespace ) => {
console.log( `Hook removed: ${ hookName } (${ namespace })` );
} );
The ‘all’ Hook (Development Only)
In non-minified builds, you can hook into all actions/filters:
import { addAction, addFilter } from '@wordpress/hooks';
// Log all actions
addAction( 'all', 'myPlugin/debugActions', ( hookName ) => {
console.log( 'Action fired:', hookName );
} );
// Log all filters
addFilter( 'all', 'myPlugin/debugFilters', ( value, hookName ) => {
console.log( 'Filter applied:', hookName, value );
return value;
} );
The ‘all’ hook is removed in production builds for performance reasons.
Naming Conventions
Hook Names
- Use only letters, numbers, dashes, periods, and underscores
- Don’t start with
__ (reserved)
- Use descriptive, hierarchical names
Good examples:
editor.blockInserted
form.validate.email
app.init.complete
Namespaces
- Format:
vendor/plugin/function
- Use only letters, numbers, dashes, periods, underscores, and slashes
- Must be unique
Good examples:
myCompany/myPlugin/logAction
wordpress/editor/modifyBlock
acme-corp/analytics/trackEvent
Best Practices
- Always use namespaces - Prevents conflicts with other plugins
- Choose descriptive hook names - Make purpose clear
- Document your hooks - Help other developers extend your code
- Use appropriate priorities - Control execution order
- Clean up hooks - Remove hooks when no longer needed
- Return values in filters - Always return a value from filter callbacks
- Don’t modify arguments - Create new objects/arrays instead
Common Patterns
Conditional Hook Registration
import { addAction } from '@wordpress/hooks';
if ( isFeatureEnabled( 'analytics' ) ) {
addAction( 'app.pageView', 'myPlugin/trackPageView', trackPageView );
}
Hook Cleanup
import { addAction, removeAction } from '@wordpress/hooks';
function setupTracking() {
addAction( 'app.event', 'myPlugin/track', trackEvent );
}
function teardownTracking() {
removeAction( 'app.event', 'myPlugin/track' );
}
Chaining Filters
import { addFilter } from '@wordpress/hooks';
// First filter
addFilter( 'content', 'plugin1/uppercase', ( text ) => {
return text.toUpperCase();
}, 10 );
// Second filter (runs after first)
addFilter( 'content', 'plugin2/addPrefix', ( text ) => {
return `PREFIX: ${ text }`;
}, 20 );
// Result: "PREFIX: HELLO WORLD"
const result = applyFilters( 'content', 'hello world' );
TypeScript Support
The package includes TypeScript definitions:
import { addFilter } from '@wordpress/hooks';
type ContentFilter = ( content: string, context: object ) => string;
addFilter<ContentFilter>(
'content.process',
'myPlugin/processContent',
( content, context ) => {
return content.toUpperCase();
}
);
Debugging
List All Registered Hooks
import { actions, filters } from '@wordpress/hooks';
console.log( 'Registered actions:', actions );
console.log( 'Registered filters:', filters );
Track Hook Usage
import { addAction, didAction, didFilter } from '@wordpress/hooks';
function logHookStats() {
console.log( 'init action fired:', didAction( 'init' ), 'times' );
console.log( 'content filter applied:', didFilter( 'content' ), 'times' );
}