The hook system allows plugins to intercept and modify core Sodium events. Hooks are priority-based and support a deny mechanism to block actions.
Use sodium.hooks.on() to subscribe to an event:
sodium.hooks.on('server:beforeCreate', async (ctx) => {
// Your logic here
}, priority);Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
event |
string |
— | The hook event name. Must be declared in plugin.json. |
handler |
(ctx) => Promise<void> |
— | Async handler function that receives the event context. |
priority |
number |
10 |
Execution order. Lower numbers run first. |
Every hook your plugin uses must be declared in the hooks array of your plugin.json:
{
"id": "my-plugin",
"name": "My Plugin",
"version": "1.0.0",
"hooks": ["server:beforeCreate", "auth:afterLogin"]
}If you try to register a hook not listed in the manifest, it will be skipped and a warning logged:
WARN [Plugin:my-plugin] Hook "server:onDelete" not declared in manifest, skipping
Fired before a server is created. Can be used to validate, modify, or deny the creation.
| Context field | Type | Description |
|---|---|---|
user |
object |
The user requesting the server creation |
name |
string |
The server name |
node |
object |
The selected node |
sodium.hooks.on('server:beforeCreate', async (ctx) => {
if (ctx.user.serverCount >= 5) {
ctx.deny('You have reached the maximum number of servers.');
}
});Fired after a server has been successfully created. Cannot deny.
| Context field | Type | Description |
|---|---|---|
server |
object |
The newly created server object |
user |
object |
The user who created the server |
sodium.hooks.on('server:onCreate', async (ctx) => {
sodium.logger.info(`Server "${ctx.server.name}" created by ${ctx.user.username}`);
});Fired when a server power action is executed (start, stop, restart, kill).
| Context field | Type | Description |
|---|---|---|
server |
object |
The server object |
action |
string |
The power action: start, stop, restart, or kill |
user |
object |
The user who triggered the action |
sodium.hooks.on('server:onStatusChange', async (ctx) => {
sodium.logger.info(`Server "${ctx.server.name}" action: ${ctx.action}`);
});Fired when a server is deleted.
| Context field | Type | Description |
|---|---|---|
server |
object |
The server being deleted |
user |
object |
The user who deleted the server |
sodium.hooks.on('server:onDelete', async (ctx) => {
sodium.logger.info(`Server "${ctx.server.name}" deleted`);
});Fired after a user successfully registers.
| Context field | Type | Description |
|---|---|---|
user |
object |
The newly registered user (without password) |
ip |
string |
The IP address of the request |
sodium.hooks.on('auth:afterRegister', async (ctx) => {
sodium.logger.info(`New user registered: ${ctx.user.username} from ${ctx.ip}`);
});Fired after a user successfully logs in.
| Context field | Type | Description |
|---|---|---|
user |
object |
The authenticated user (without sensitive fields) |
ip |
string |
The IP address of the request |
method |
string |
The authentication method (e.g., password) |
sodium.hooks.on('auth:afterLogin', async (ctx) => {
sodium.db.insert('login_logs', {
userId: ctx.user.id,
ip: ctx.ip,
method: ctx.method,
timestamp: Date.now()
});
});Hooks execute in ascending priority order (lower numbers first). This lets you control execution order when multiple plugins subscribe to the same event:
// Runs first (priority 1)
sodium.hooks.on('server:beforeCreate', async (ctx) => {
// validation logic
}, 1);
// Runs second (priority 10 — the default)
sodium.hooks.on('server:beforeCreate', async (ctx) => {
// logging logic
});
// Runs last (priority 100)
sodium.hooks.on('server:beforeCreate', async (ctx) => {
// cleanup logic
}, 100);For "before" hooks (like server:beforeCreate), handlers receive a context object with a deny() method. Calling deny(reason) stops the hook chain and prevents the action:
sodium.hooks.on('server:beforeCreate', async (ctx) => {
const maintenanceMode = sodium.config.get('maintenanceMode');
if (maintenanceMode) {
ctx.deny('Server creation is disabled during maintenance.');
// No further hooks will execute after this
return;
}
});The deny context fields:
| Field | Type | Description |
|---|---|---|
ctx._denied |
boolean |
Whether the action was denied |
ctx._denyReason |
string | null |
The reason passed to deny() |
ctx.deny(reason) |
function |
Call to deny the action and stop the hook chain |
If a hook handler throws an error, the error is caught and logged. Execution continues with the next handler:
WARN [Plugins] Hook "server:beforeCreate" error: Cannot read property 'x' of undefined
This means one plugin's error will not break other plugins or the core system.
When a plugin is deactivated, all its hooks are automatically removed. You do not need to manually unsubscribe.