Hooks
Change how Kora behaves from a module without touching the framework.
The framework never checks for a specific module. When a module needs to change core behavior, it registers a hook on this.kora.hooks. Remove the module and its hooks go with it.
async onStartup() {
this.kora.hooks.register("restrict", this.restrict);
}
async onShutdown() {
this.kora.hooks.unregister("restrict", this.restrict);
}Register in onStartup() and unregister in onShutdown() with the same function, so turning the module off removes it.
Registry
| Method | What it does |
|---|---|
register(name, handle) | Adds a handle under name. |
unregister(name, handle) | Removes it. |
has(name) | Whether any handle is registered. |
get(name, ...args) | Calls each handle in order and returns the first result that is not undefined. |
value(name, ...args) | The same as get, without waiting. Handles must return plain values. |
run(name, ...args) | Calls every handle and ignores the results. |
restricted(action, context) | Asks the restrict hooks about action. Returns the reason as a string, or "" when allowed. |
assert(action, context) | Throws the reason when action is restricted. |
Hooks Kora Calls
| Hook | Call | Arguments | Return |
|---|---|---|---|
setup | value | () | Setup needs, such as [] to skip the setup wizard. |
owner | value | () | The owner { discordId, name } in place of the stored one. |
session | get | (request) | A user for visitors with no session. Kora marks it guest: true. |
permissions | get | (user, session) | Fields merged over the signed-in staff session: permissions, role, canSwitch, and optionally user, kind, customer, discordId. Skips the core identity lookup. |
customer | get | (request) | Customer ID used by the client licenses preview. null shows the latest entries. |
restrict | restricted | (action, context) | A reason string to block the action, or undefined to allow it. |
mode | get | ({ session }) | { badge, hint, link, server }. Shows a badge next to the brand. badge and hint are lang keys, link and server are { label, href } or null. |
Restrictions
Each action below is passed to restricted() before it runs.
| Action | Covers |
|---|---|
action:<name> | A dashboard action, such as action:saveSettings. |
write | Every dashboard write through the API proxy. Context: { session, discordId, module }. |
page:<module>:<name> | Hides a module page from the sidebar. |
license | The online license check at boot. |
login | The login page. |
logs | The Logs page and its API. |
owner | Owner-only settings. |
modules, tokens, export, settings | Locks those dashboard sections. |
licenses.file | Downloading an offline license file. |
discord | Starting the Discord bot. |
restart | Scheduled auto restarts. |
The shell receives restricted, the subset of logs, owner, modules, tokens, export and settings that is blocked, so the dashboard can hide those controls.
Demo Module
Every product ships its demo as the demo module on the demo/module branch. Customer releases leave it out. The demo build adds it and sets DEMO=true.
The demo module:
- Sets
hidden: truein its manifest so it never shows on the Modules page. - Registers its hooks in
onBoot()only whenprocess.env.DEMO === "true", and unregisters them inonShutdown(). Itssetup,ownerandlicensehooks let a demo instance start without a license or the setup wizard. - Lists blocked actions in
actionsand blocked restrictions indenied. Anything not listed is allowed. - Returns its
messages.lockedphrase as the reason, and keeps its badge text inresources/lang/<code>/common.json.
It also registers customer and a persona-aware permissions hook, so the persona switch moves between a staff and a customer view.
restrict(action) {
if (this.denied.includes(action)) return this.kora.modules.lang.phrase("demo", "messages", "locked");
if (action.startsWith("action:") && this.actions.includes(action.slice(7))) return this.kora.modules.lang.phrase("demo", "messages", "locked");
return undefined;
}