Last Updated: October 2026
Client hooks are a browser-side event system that plugins use to modify UI behaviour, share data, or communicate with each other. They mirror the server hook API but run entirely in the browser.
Overview
Client hooks are accessed via window.__pokerEngine.hooks:
const { hooks } = window.__pokerEngine;
// Filter — receives a value, must return it (possibly modified)
hooks.addFilter('hook-name', (value, ...args) => {
return modifiedValue;
}, options);
// Action — fire-and-forget, return value is ignored
hooks.addAction('hook-name', (...args) => {
doSomething();
}, options);
All hooks registered with a providerType are automatically removed when the plugin is deactivated. Always pass providerType in options.
ClientHookRegistry API
addFilter(hook, callback, options?)
Registers a filter callback. Filters run in priority order — each callback receives the value returned by the previous one. Filters are synchronous, so do not return a Promise. If a callback throws, the error is logged and the chain continues with the previous value.
hooks.addFilter('my-plugin:format-balance', (amount, currency) => {
return `${currency} ${parseFloat(amount).toFixed(2)}`;
}, { providerType: 'my-plugin', priority: 10 });
| Parameter | Type | Description |
|---|---|---|
hook | string | Hook name |
callback | function | (value, ...args) => value — must return the value |
options.priority | number | Execution order — lower runs first (default: 10) |
options.providerType | string | Your plugin’s providerType — required for cleanup |
applyFilters(hook, value, ...args)
Runs all registered filter callbacks for a hook and returns the final value. Used by the host app and by plugins to apply a filter chain.
const formattedAmount = hooks.applyFilters('my-plugin:format-balance', '100.00', 'USD');
addAction(hook, callback, options?)
Registers an action callback. Actions are fire-and-forget — return values are ignored. Callbacks may be async.
hooks.addAction('my-plugin:deposit-complete', (deposit) => {
analytics.track('deposit', { amount: deposit.amount });
}, { providerType: 'my-plugin', priority: 10 });
doAction(hook, ...args)
Fires all registered action callbacks for a hook in priority order, awaiting each one. Returns a Promise<void> that resolves after all callbacks (including async ones) complete. A callback that throws is logged and does not stop the others.
await hooks.doAction('my-plugin:deposit-complete', { amount: '100.00' });
removeAction(hook, callback) / removeFilter(hook, callback)
Removes a specific callback by reference. Prefer using providerType for bulk cleanup on deactivation.
unregisterPlugin(providerType)
Removes all actions and filters registered by a given providerType. Called automatically by usePluginSync when a plugin:deactivated, plugin:auto-disabled or plugin:updated event is received.
Host Application Hooks
The following hooks are fired by the host application and can be consumed by plugins.
nav:items:user (Filter) — Beta
Filters the list of user-facing plugin navigation items before they are rendered in the main sidebar. Plugins can use this hook to add, remove, reorder, or modify nav items.
Value type: Array<{ key: string; to: string; providerType: string; path: string; icon?: string; label: string; badge?: string }>
hooks.addFilter('nav:items:user', (items) => {
// Remove a specific page from the nav
return items.filter(item =>
!(item.providerType === 'my-plugin' && item.path === '/hidden-page')
);
}, { providerType: 'my-plugin', priority: 10 });
Inter-Plugin Hooks
In addition to host application hooks, the client hook registry serves as an inter-plugin communication bus. Plugins can define and fire their own hooks for coordination.
Pattern: Plugin A exposes a hook that Plugin B consumes
Plugin A (fires the hook):
// In plugin-a's frontend entry
const { hooks } = window.__pokerEngine;
async function loadTableData(tableId) {
const rawData = await fetchTable(tableId);
// Let other plugins transform the data before rendering
const processedData = hooks.applyFilters('plugin-a:table-data', rawData, { tableId });
renderTable(processedData);
}
Plugin B (consumes the hook):
// In plugin-b's frontend entry
const { hooks } = window.__pokerEngine;
hooks.addFilter('plugin-a:table-data', (data, context) => {
// Inject extra field from plugin-b
return { ...data, customField: computeCustomField(data) };
}, { providerType: 'plugin-b', priority: 10 });
Pattern: Broadcast events between plugins
Plugin A (broadcasts):
hooks.doAction('plugin-a:user-identified', { userId: '123', tier: 'vip' });
Plugin B (listens):
hooks.addAction('plugin-a:user-identified', ({ userId, tier }) => {
updateUserDisplay(userId, tier);
}, { providerType: 'plugin-b' });
Naming Conventions
Use {providerType}:{event-name} for plugin-defined hooks to avoid collisions:
my-plugin:settings-loaded ✓
my-plugin:user-identified ✓
settings-loaded ✗ (too generic, may collide)
user ✗ (not descriptive enough)
Lifecycle Sync
Client hooks are synchronised with the backend plugin state via WebSocket events. When a plugin is deactivated (manually or via circuit breaker) or upgraded, the usePluginSync hook calls:
fillRegistry.unregisterPlugin(providerType);
pageRegistry.unregisterPlugin(providerType);
clientHooks.unregisterPlugin(providerType);
This removes all the plugin’s registered hooks, slot fills, and pages from the frontend without a page reload, along with its injected script tags. On upgrade, the new bundle is then loaded.
Related
| Resource | Link |
|---|---|
| Plugin Development Guide | developer/plugins |
| Server Hooks Reference | developer/plugins-hooks-server |
| Slot Fills Reference | developer/plugins-slot-fills |
| Hook registry source | client/src/core/hooks/client-hook-registry.ts |
| Plugin sync source | client/src/hooks/usePluginSync.ts |