A plugin hook registry is the extension mechanism a host app exposes so plugins can change what it does without editing its source: the app fires named hooks at key moments, and plugins attach functions to those names. It is the core of WordPress's hooks and webpack's Tapable. You will build one as a factory pluginHookRegistry(), supporting the two hook kinds these systems share. An action is a fire-and-forget notification — a post was saved — that runs every attached listener for its side effects. A filter is a value transformer — here is a title, hand back a possibly-changed one — that threads a value through every attached function and returns the final result.
const hooks = pluginHookRegistry();
// Actions: fire-and-forget side effects.
hooks.addAction(name, fn, priority?); // priority default 10; lower runs first
hooks.doAction(name, ...args); // run every listener; return values ignored
hooks.removeAction(name, fn); // unregister a specific listener
hooks.hasAction(name); // -> boolean
// Filters: value transformers.
hooks.addFilter(name, fn, priority?); // priority default 10; lower runs first
hooks.applyFilter(name, value, ...args); // -> the value after every transformer runs
hooks.removeFilter(name, fn); // unregister a specific transformer
hooks.hasFilter(name); // -> boolean
const hooks = pluginHookRegistry();
// Actions: two plugins react to the same event; both listeners run.
hooks.addAction('post.saved', (post) => sendEmail(post));
hooks.addAction('post.saved', (post) => clearCache(post));
hooks.doAction('post.saved', { id: 7 }); // both run, each gets { id: 7 }
// Filters: each function receives the previous value and returns the next.
hooks.addFilter('title', (t) => t.trim());
hooks.addFilter('title', (t) => t.toUpperCase());
hooks.applyFilter('title', ' hello '); // 'HELLO'
// Priority controls order: lower runs first.
hooks.addFilter('price', (p) => p + 10, 20);
hooks.addFilter('price', (p) => p * 2, 5); // priority 5 runs before priority 20
hooks.applyFilter('price', 100); // (100 * 2) + 10 = 210
doAction runs every listener for its side effects and ignores what they return. applyFilter threads value through each function, using each return value as the next input, and returns the final value.10. A hook at priority 5 runs before one at 10. Hooks registered at the same priority must run in the order they were added — ties stay stable.applyFilter behaves as identity until a plugin hooks that name, so filtering a value is always safe.doAction never runs filters and applyFilter never runs actions.