Plugin Hook RegistryLoading saved progress…

Plugin Hook Registry

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.

Signature

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

Examples

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

Notes

  • Two kinds, two behaviorsdoAction 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.
  • Priority is a number; lower runs first — the default is 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.
  • Filters compose, so order changes the result — add-then-double and double-then-add give different answers for the same input. Getting the ordering right is the crux of this problem.
  • Applying a filter with nothing registered returns the value unchangedapplyFilter behaves as identity until a plugin hooks that name, so filtering a value is always safe.
  • Actions and filters are separate namespaces — an action and a filter can share a name without interfering; doAction never runs filters and applyFilter never runs actions.
  • Keep it synchronous — no promises, no timers. Each listener and filter runs to completion before the next one starts.

FAQ

What is the difference between an action and a filter?
An action is fire-and-forget: doAction runs every listener for its side effects and ignores what they return. A filter is a value transformer: applyFilter threads a value through each function, using each return value as the next input, and returns the final result.
How does hook priority work?
Every hook is registered with a priority number (default 10). Lower numbers run earlier. Hooks registered at the same priority run in the order they were added, so ties stay stable.
What does applyFilter return when no filters are registered?
It returns the value unchanged. With no transformers to run, the original value passes straight through, so filtering a value is always safe even when no plugin has hooked that name.
Loading editor…