> ## Documentation Index
> Fetch the complete documentation index at: https://docs.seraph.si/llms.txt
> Use this file to discover all available pages before exploring further.

# Manifest & permissions

> Declare a plugin, ask for the permissions it needs and expose its settings.

## declareModule

Call `declareModule` once, at the top of the file, before anything else. It is what gives your
plugin a name, puts it in the Plugins tab and tells Seraph what it intends to use.

```js theme={null}
declareModule({
    name: "party-greeter",
    displayName: "Party Greeter",
    version: "1.0.0",
    author: "you",
    description: "Greets people as they join your party.",
    permissions: ["chat", "commands"],
    dependsOn: [],
    config: {
        greeting: "welcome!",
        onlyInParty: true,
    },
});
```

| Field         | Required | What it does                                                                |
| ------------- | -------- | --------------------------------------------------------------------------- |
| `name`        | Yes      | The identifier. Used by `dependsOn`, by `require` and as the storage key.   |
| `displayName` | No       | The name shown in the Plugins tab. Falls back to `name`.                    |
| `version`     | No       | Shown alongside the plugin.                                                 |
| `author`      | No       | Shown alongside the plugin.                                                 |
| `description` | No       | Shown in the Plugins tab, so write it for the person deciding to enable it. |
| `permissions` | No       | What the plugin is allowed to do. Anything not listed here is refused.      |
| `dependsOn`   | No       | Other plugins that must load first.                                         |
| `config`      | No       | Default settings, editable in game and readable through `config`.           |
| `secrets`     | No       | Config keys the menu hides behind a reveal button.                          |

Without `declareModule` the file still runs, but it is named after itself and holds no
permissions, so every gated call it makes is refused.

## Permissions

A gated call only goes through when all three of these are true: the master switch is on, that
plugin is enabled, and the plugin declared the matching permission. Ask for the least you need,
users see the whole list before they enable anything.

| Permission  | Allows                                           | Reached through          |
| ----------- | ------------------------------------------------ | ------------------------ |
| `chat`      | Read and send chat messages                      | `chat`                   |
| `commands`  | Register and run chat commands                   | `events.registerCommand` |
| `player`    | Read and control the local player                | `player`, `movement`     |
| `world`     | Read and interact with the world and entities    | `world`                  |
| `network`   | Make network requests                            | `http`, `stats`          |
| `storage`   | Read and write its own persistent storage        | `storage`                |
| `render`    | Draw overlays on your screen                     | `render`, `nametags`     |
| `anticheat` | Register custom checks and receive flag events   | `anticheat`              |
| `tray`      | Show system tray icons and toast notifications   | `tray`                   |
| `notify`    | Show titles and action bar text, and play sounds | `hud`                    |

`console`, `scheduler`, `input`, `text`, `party` and the event listeners themselves are ungated.

A refused call is written to the log once per plugin and permission, naming what was attempted, so
a plugin that quietly does nothing is usually a missing entry in `permissions`.

### Network rate limit

The `network` permission is additionally rate limited to **30 requests per minute** per plugin,
counted across `http.fetch` and `stats`, so a plugin cannot spam an endpoint even once you have
granted it access.

## Config

Anything you put in `config` becomes a default setting. Users edit it from the plugin's own menu in
the Plugins tab, and your plugin reads it through the global `config` object:

```js theme={null}
declareModule({
    name: "party-greeter",
    permissions: ["chat"],
    config: {
        greeting: "welcome!",
        onlyInParty: true,
    },
});

events.on("chat", (event) => {
    if (config.onlyInParty && !party.isInParty()) return;
    chat.addChatMessage(config.greeting);
});
```

The object is live: read it when you need a value rather than copying it into a variable at load
time, and a change made in game takes effect straight away.

Writing to `config` is allowed, and `savePluginConfig()` writes the changes to disk so they
survive a restart:

```js theme={null}
config.greeting = "hello again";
savePluginConfig();
```

Config is stored per plugin under `plugins/config`.

### Hiding a value

Name a config key in `secrets` and the menu draws it as dots with an eye button beside it, the same
as the API key field, so a webhook or a token is not read off the screen during a share:

```js theme={null}
declareModule({
    name: "webhook-relay",
    permissions: ["network"],
    config: {
        webhook: "",
        delay: 20,
    },
    secrets: ["webhook"],
});
```

Only the drawing changes. The value is stored in `plugins/config` and read through `config` like
any other setting, so treat this as a guard against an accident rather than a way of keeping a
secret from whoever is at the keyboard. A hidden field is still editable while it is masked, and a
key that is not part of `config` is ignored.

## Dependencies

List other plugins in `dependsOn` and Seraph loads them first, so their exports are ready by the
time your file runs:

```js theme={null}
declareModule({ name: "stats-hud", dependsOn: ["stats-core"] });

const core = require("stats-core");
```

If something you depend on is missing or disabled your plugin is skipped entirely, with the reason
logged, rather than failing half way through. Plugins that depend on each other in a circle are all
still loaded, but the order between them is arbitrary and the circle is reported.
