Kora/developer documentation/Hooks

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.

modules/example/main.js
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

MethodWhat 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

HookCallArgumentsReturn
setupvalue()Setup needs, such as [] to skip the setup wizard.
ownervalue()The owner { discordId, name } in place of the stored one.
sessionget(request)A user for visitors with no session. Kora marks it guest: true.
permissionsget(user, session)Fields merged over the signed-in staff session: permissions, role, canSwitch, and optionally user, kind, customer, discordId. Skips the core identity lookup.
customerget(request)Customer ID used by the client licenses preview. null shows the latest entries.
restrictrestricted(action, context)A reason string to block the action, or undefined to allow it.
modeget({ 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.

ActionCovers
action:<name>A dashboard action, such as action:saveSettings.
writeEvery dashboard write through the API proxy. Context: { session, discordId, module }.
page:<module>:<name>Hides a module page from the sidebar.
licenseThe online license check at boot.
loginThe login page.
logsThe Logs page and its API.
ownerOwner-only settings.
modules, tokens, export, settingsLocks those dashboard sections.
licenses.fileDownloading an offline license file.
discordStarting the Discord bot.
restartScheduled 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: true in its manifest so it never shows on the Modules page.
  • Registers its hooks in onBoot() only when process.env.DEMO === "true", and unregisters them in onShutdown(). Its setup, owner and license hooks let a demo instance start without a license or the setup wizard.
  • Lists blocked actions in actions and blocked restrictions in denied. Anything not listed is allowed.
  • Returns its messages.locked phrase as the reason, and keeps its badge text in resources/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.

modules/demo/src/handlers/policy.js
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;
}