Last Updated: October 2026
This page documents every server-side action and filter hook that the poker engine fires. Plugins register for hooks via registry.addAction() and registry.addFilter().
Overview
┌──────────────────────────────────────────────────────────────┐
│ Hook Types │
│ │
│ Action registry.addAction(hook, callback, priority?) │
│ - Fire-and-forget; callback return value ignored │
│ - Callback may be async │
│ │
│ Filter registry.addFilter(hook, callback, priority?) │
│ - Callback MUST return the (possibly modified) │
│ first argument │
│ - Filters chain in priority order; synchronous │
└──────────────────────────────────────────────────────────────┘
Lower priority numbers run first (default: 10).
Startup hooks — hooks marked ⚠ fire during server initialisation. When a plugin is activated at runtime, the engine replays its
server:routes:*andserver:readycallbacks with the recorded arguments, so they still run.server:bootis not replayed; only plugins loaded at startup receive it. Replay does not happen on upgrade: restart the server after upgrading a plugin that relies on these hooks.
Server Lifecycle
server:boot ⚠
Type: Action
When: After plugins load and before the Express application is created. Not replayed for plugins activated at runtime.
Arguments: none
Source: src/index.ts
Use this to perform setup that must happen before any routes or middleware are registered.
registry.addAction('server:boot', () => {
registry.logger.info('Early boot hook called');
});
server:ready ⚠
Type: Action
When: After server.listen() completes and the WebSocket server is running.
Arguments: server: HttpServer
Source: src/index.ts (dispatched through src/core/plugins/plugin-lifecycle.service.ts)
The server argument is the raw Node.js http.Server instance. Use it to attach additional WebSocket listeners or to record the listening address.
registry.addAction('server:ready', (server) => {
const addr = server.address();
registry.logger.info(`Server ready on port ${addr.port}`);
});
server:shutdown
Type: Action
When: When a SIGTERM or SIGINT signal is received. The engine enters graceful shutdown after all server:shutdown callbacks return. Any plugin loaded at that moment receives it, including plugins activated at runtime.
Arguments: none
Source: src/index.ts
Use this to flush queues, close external connections, or write final state before the process exits.
registry.addAction('server:shutdown', async () => {
await myExternalClient.disconnect();
registry.logger.info('Plugin shut down cleanly');
});
Route Registration
These hooks fire once per route module during startup, and are replayed for plugins activated at runtime. Use them to append additional Express routes or middleware to an existing router.
server:routes:auth ⚠
Type: Action
When: After the auth router is mounted at /api/v1/auth.
Arguments: router: Router
Source: src/app.ts
registry.addAction('server:routes:auth', (router) => {
router.post('/custom-login', async (req, res) => {
// Custom authentication endpoint appended to /api/v1/auth/
res.json({ ok: true });
});
});
server:routes:cashier ⚠
Type: Action
When: After the cashier router is mounted at /api/v1/cashier.
Arguments: router: Router
Source: src/app.ts
registry.addAction('server:routes:cashier', (router) => {
router.get('/my-provider/rates', async (req, res) => {
res.json({ rate: 1.0 });
});
});
server:routes:admin ⚠
Type: Action
When: At the end of the admin sub-router composition.
Arguments: router: Router, a dedicated router under /api/v1/admin with authenticate and requireAdmin already applied.
Source: src/api/routes/admin/plugin-admin-router.ts
registry.addAction('server:routes:admin', (router) => {
router.get('/my-plugin/report', async (req, res) => {
res.json({ data: [] });
});
});
server:routes:api ⚠
Type: Action
When: After all route modules are mounted on the Express app.
Arguments: router: Router, a dedicated router mounted at the root of the app.
Source: src/app.ts
This is the catch-all route hook. Use it when you need routes outside /api/v1/plugins/{providerType} and outside a specific route module. No auth is applied.
registry.addAction('server:routes:api', (router) => {
router.use('/custom-prefix', myRouter);
});
Plugin Lifecycle
These hooks fire as the engine activates, deactivates, or force-disables plugins.
plugin:activated
Type: Action
When: After a plugin is activated through the admin panel and its database record is updated. Does not fire for plugins loaded at startup or on upgrade.
Arguments: providerType: string
Source: src/core/plugins/plugin-manager.service.ts
registry.addAction('plugin:activated', (providerType) => {
if (providerType === 'my-dependency') {
// A plugin your plugin depends on just became active
initDependentFeature();
}
});
plugin:deactivating
Type: Action
When: During deactivation through the admin panel, after the active-payment-system check and before the plugin’s hooks and routes are removed.
Arguments: providerType: string
Source: src/core/plugins/plugin-manager.service.ts
This is the hook to use for a plugin’s own cleanup, such as removing an external webhook. Guard on providerType, because it fires for every plugin.
registry.addAction('plugin:deactivating', async (providerType) => {
if (providerType !== registry.providerType) return;
await deleteExternalWebhook();
});
plugin:deactivated
Type: Action
When: Immediately after a plugin has been deactivated and its hooks/routes cleaned up. The deactivated plugin’s own callbacks no longer receive it.
Arguments: providerType: string
Source: src/core/plugins/plugin-manager.service.ts
plugin:auto-disabled
Type: Action
When: When the circuit breaker triggers — the plugin reached 50 errors within a 5-minute window. The disabled plugin’s own callbacks no longer receive it.
Arguments: providerType: string
Source: src/core/plugins/circuit-breaker.ts
registry.addAction('plugin:auto-disabled', (providerType) => {
registry.logger.error(`Plugin ${providerType} was auto-disabled`);
// Notify an external alerting system
});
Settings
plugin:settings:saved
Type: Action
When: After a plugin’s settings have been validated, encrypted, and written to the database. Fires for every plugin’s save.
Arguments: providerType: string, settings: Record<string, string>
Source: src/api/routes/admin/plugins.routes.ts
settings contains the plain-text values written by this save, including passwords. A password left unchanged is not written, so it is omitted; read it with registry.settings.get().
Always guard against other plugins’ settings by checking providerType:
registry.addAction('plugin:settings:saved', async (providerType, settings) => {
if (providerType !== registry.providerType) return;
reinitialiseApiClient(settings.apiKey, settings.apiSecret ?? await registry.settings.get('apiSecret'));
});
plugin:settings:validate
Type: Filter
When: Before settings are written to the database, after built-in validation.
Initial value: { valid: true, errors: string[] }, where errors holds any built-in validation errors
Additional arguments: settings: Record<string, string>, providerType: string
Expected return: The (possibly modified) { valid, errors } object
Source: src/api/routes/admin/plugins.routes.ts
If errors is not empty when all filter callbacks have run, the settings save is rejected and the errors are returned to the admin UI. Setting valid to false without adding an error does not reject the save.
Only the callbacks registered by the plugin being saved run, so you do not need a providerType guard. A password left unchanged arrives as ********.
registry.addFilter('plugin:settings:validate', (result, settings) => {
if (!settings.apiKey) {
result.errors.push('API key is required');
return result;
}
if (!settings.apiKey.startsWith('sk_live_') && settings.environment === 'production') {
result.errors.push('A live API key is required for the production environment');
}
return result;
});
Discovery Filters
auth:providers
Type: Filter
When: On GET /api/v1/auth/providers.
Initial value: string[], starting as ['local']
Source: src/api/routes/auth.routes.ts
cashier:deposit:methods / cashier:withdraw:methods
Type: Filter
When: On GET /api/v1/cashier/payment-methods.
Initial value: Array of active PaymentSystemConfig objects for the direction. See Extension Points for the fields the route reads.
Source: src/api/routes/cashier.routes.ts
Game Lifecycle Hooks
Game Hooks
Stability: Experimental — These hooks are observation-only. Callbacks must not throw or block.
Game hooks fire after internal game events. They run asynchronously and do not block the game loop.
registry.addAction('game:hand:completed', async (data: unknown) => {
const { tableId, handNumber, winners } = data as {
tableId: string;
handNumber: number;
winners: Array<{ playerId: string; amount: number }>;
};
// Log, update statistics, trigger achievements, etc.
registry.logger.info(`Hand #${handNumber} completed on table ${tableId}`);
});
| Hook | Arguments | When |
|---|---|---|
game:hand:started | { tableId: string, handNumber: number, dealerSeat: number } | After a new hand begins |
game:hand:completed | { tableId: string, handNumber: number, winners: Array<{ playerId: string; amount: number }> } | After a hand completes |
game:player:seated | { tableId: string, userId: string, seatIndex: number } | After a player joins a table |
game:player:left | { tableId: string, userId: string, reason: 'voluntary' | 'forced' } | After a player leaves a table |
Plugin-to-Plugin Communication (Server)
Plugins can fire their own namespaced hooks using registry.doAction(). Other plugins can listen with registry.addAction().
Hooks are automatically namespaced to plugin:{providerType}:* — a plugin cannot fire hooks outside its own namespace.
// In plugin A (providerType: 'steem'):
registry.doAction('plugin:steem:deposit-confirmed', { userId, amount });
// In plugin B (any providerType):
registry.addAction('plugin:steem:deposit-confirmed', async (data: unknown) => {
// React to steem deposits
});
Hook Summary
| Hook | Type | Startup | Arguments |
|---|---|---|---|
server:boot | Action | ⚠ | — |
server:ready | Action | ⚠ | server: HttpServer |
server:shutdown | Action | — | |
server:routes:auth | Action | ⚠ | router: Router |
server:routes:cashier | Action | ⚠ | router: Router |
server:routes:admin | Action | ⚠ | router: Router |
server:routes:api | Action | ⚠ | router: Router |
plugin:activated | Action | providerType: string | |
plugin:deactivating | Action | providerType: string | |
plugin:deactivated | Action | providerType: string | |
plugin:auto-disabled | Action | providerType: string | |
plugin:settings:saved | Action | providerType: string, settings: Record<string, string> | |
plugin:settings:validate | Filter | value: { valid, errors }, settings, providerType | |
auth:providers | Filter | value: string[] | |
cashier:deposit:methods | Filter | value: method array | |
cashier:withdraw:methods | Filter | value: method array | |
game:hand:started | Action | { tableId, handNumber, dealerSeat } | |
game:hand:completed | Action | { tableId, handNumber, winners: Array<{ playerId, amount }> } | |
game:player:seated | Action | { tableId, userId, seatIndex } | |
game:player:left | Action | { tableId, userId, reason: 'voluntary' | 'forced' } |
Related
| Resource | Link |
|---|---|
| Plugin Development Guide | developer/plugins |
| Client Hooks Reference | developer/plugins-hooks-client |
| Plugin Lifecycle Reference | developer/plugins-lifecycle |
| Hook registry source | src/core/hooks/hook-registry.ts |