# Hooks

URL: https://zerodev.ca/docs/medusa/developer-documentation/hooks

> Change how Medusa 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.medusa.hooks`. Remove the module and its hooks go with it.

```js title="modules/example/main.js"
async onStartup() {
    this.medusa.hooks.register("restrict", this.restrict);
}

async onShutdown() {
    this.medusa.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 Medusa Calls

| Hook          | Call         | Arguments           | Return                                                                                                                                                   |
| ------------- | ------------ | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `session`     | `get`        | `(request)`         | A user for visitors with no session. Medusa marks it `guest: true`.                                                                                      |
| `permissions` | `get`        | `(discordId)`       | Permission list for that user. Skips the server role lookup.                                                                                             |
| `signup`      | `get`        | `(discordId)`       | Runs when a new dashboard user signs in.                                                                                                                 |
| `servers`     | `get`        | `(discordId)`       | Server list shown in the server picker.                                                                                                                  |
| `scan`        | `run`        | `(value, label)`    | Gets every value saved from the dashboard or the API, such as config, phrases, presence and the bot username.                                            |
| `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`.                                     |
| `page:<module>:<name>`                    | Hides a module page from the sidebar.                                                  |
| `login`                                   | The login page.                                                                        |
| `logs`                                    | The **Logs** page and its API.                                                         |
| `owner`                                   | Owner-only settings.                                                                   |
| `modules`, `tokens`, `export`, `settings` | Locks those dashboard sections.                                                        |
| `write`                                   | Every dashboard write through the API proxy. Context: `{ session, discordId, guild }`. |
| `automation`                              | Saving automation flows.                                                               |
| `appeals.server`                          | Choosing the appeals server.                                                           |
| `servers.primary`                         | Changing the primary server.                                                           |

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 `onStartup()` only when `process.env.DEMO === "true"`, and unregisters them in `onShutdown()`.
* 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 adds trial servers: `mode.server` links to `/m/demo/trial`, and `mode.link` points to the public bot invite.

```js title="modules/demo/src/handlers/policy.js"
restrict(action) {
    if (this.denied.includes(action)) return this.medusa.modules.lang.phrase("demo", "messages", "locked");
    if (action.startsWith("action:") && this.actions.includes(action.slice(7))) return this.medusa.modules.lang.phrase("demo", "messages", "locked");
    return undefined;
}
```
