# Server plugin API

Source: https://docs.gryt.chat/docs/build/server-plugin-api

Every capability, event and call a server plugin gets, generated from the source

{/* Generated by .github/scripts/generate-api-reference.mjs in the monorepo.
    Do not edit this file. Change the source it reads, then run the script. */}


<Callout type="info">
  Generated from the source and regenerated by CI, so it cannot fall behind the
  code. Editing this page by hand gets the edit wiped on the next run — change
  what the generator reads instead.
</Callout>

This is the reference. [Server plugins](https://docs.gryt.chat/docs/build/server-plugins) is the page that
teaches the shape, and [plugin pairs](https://docs.gryt.chat/docs/build/plugin-pairs) covers talking
to a copy of yourself in somebody's client.

A plugin starts by exporting `activate`, or a default export, and is called with
the object below.

```js
export function activate(api) {
  api.log.info(`${api.id} is up`);
}
```

## Capabilities

What a manifest may ask for. A name this server has never heard of is dropped
rather than refused, so a plugin written against a newer Gryt still loads.

| Capability | What the operator reads |
| --- | --- |
| `messages:read` | Read every message sent in a channel |
| `members:read` | See who joins and leaves, and the invite code they used |
| `moderation` | Kick and ban members and delete their messages, but not a moderator's |
| `messaging` | Exchange its own messages with the copy of itself in people's clients |

## The manifest

`manifest.json`, beside your entry point.

| Field | Type | What it is |
| --- | --- | --- |
| `id` | `string` | Folder name by convention, and the key the log prefix, config section and storage namespace all hang off. |
| `name` | `string` |  |
| `version` | `string` |  |
| `main` | `string` | Entry point, relative to the plugin's own folder. |
| `description?` | `string` |  |
| `author?` | `string` |  |
| `homepage?` | `string` | Members are shown this as a link, so `http` or `https` and nothing else: a `javascript:` URL here is an injection into every client. |
| `capabilities` | `PluginCapability[]` | Normalised: deduplicated, in catalogue order, unknown names dropped. |

## `api`

### `id`

```ts
id: string
```

This plugin's own id, so it does not have to hard-code the folder name.

### `capabilities`

```ts
capabilities: readonly PluginCapability[]
```

What the manifest declared, normalised. Readable so a plugin can degrade.

### `on()`

```ts
on<E extends PluginEventName>(event: E, handler: PluginEventHandler<E>): void
```

Throws at subscribe rather than failing silently at delivery, so the stack
trace at startup names the line.

### `moderation`

```ts
moderation: PluginModeration
```

Throws on access without the `moderation` capability, so a plugin finds
out when it reaches for this, not on the first member it acts on.

### `messaging`

```ts
messaging: PluginMessaging
```

What arrives here was written by a member's client, and the transport caps
only size and rate. Do not assume your own client half sent it.

### `log`

```ts
log: PluginLogger
```

Goes to the server log, prefixed with the plugin id.

## Events

`api.on(name, handler)`. Subscribing throws if the manifest did not declare the
capability behind the event, rather than failing quietly at delivery.

### `message:created`

Needs `messages:read`.

```ts
api.on("message:created", (payload) => {})
```

| Field | Type | What it is |
| --- | --- | --- |
| `messageId` | `string` |  |
| `channelId` | `string` |  |
| `userId` | `string` | The member's id on this server, not their identity key. |
| `nickname` | `string \| null` |  |
| `text` | `string` |  |
| `attachmentCount` | `number` |  |
| `at` | `string` |  |

### `member:joined`

Needs `members:read`.

```ts
api.on("member:joined", (payload) => {})
```

| Field | Type | What it is |
| --- | --- | --- |
| `userId` | `string` |  |
| `nickname` | `string \| null` |  |
| `inviteCode` | `string \| null` | Null when they did not come in through an invite. |
| `at` | `string` |  |

### `member:left`

Needs `members:read`.

```ts
api.on("member:left", (payload) => {})
```

| Field | Type | What it is |
| --- | --- | --- |
| `userId` | `string` |  |
| `nickname` | `string \| null` |  |
| `reason` | `"left" \| "kicked" \| "banned"` | A plugin logging departures wants a different line for each, and one deciding whether to act wants to know a human already did. |
| `at` | `string` |  |

## `api.moderation`

Needs `moderation`. Reading the property throws without it, rather than handing
back an object whose every call refuses.

A call returns an outcome rather than throwing. A refusal is an ordinary answer
— the member can moderate, or is already gone — and a plugin should log it and
carry on.

### `kick()`

```ts
kick(serverUserId: string, options?: { reason?: string }): Promise<ModerationOutcome>
```

Remove somebody from the server. They can come back.

### `ban()`

```ts
ban( serverUserId: string, options?: { reason?: string; durationMs?: number }, ): Promise<ModerationOutcome>
```

Remove somebody and stop them returning.

### `deleteMessage()`

```ts
deleteMessage(channelId: string, messageId: string): Promise<ModerationOutcome>
```

Channels only, the same rule that keeps DMs out of `message:created`.

## `api.messaging`

Needs `messaging`. The pipe to the client half of this plugin.

### `on()`

```ts
on(topic: string, handler: PluginMessageHandler): void
```

Hear what the client half of this plugin sends.

### `send()`

```ts
send(topic: string, data: unknown, target?: SendTarget): boolean
```

False when refused, with the reason in the log. Not a promise: there is no
delivery to wait for.

### What a handler is given

| Field | Type | What it is |
| --- | --- | --- |
| `topic` | `string` | **The sender picked it**, within the limits below.  |
| `data` | `unknown` | **The member's own bytes — check it.** Not validated beyond its size: a member's own bytes, and the plugin's to check. |
| `userId` | `string` | **From the connection**, so it cannot be faked. Who sent it, as their id on this server. |
| `nickname` | `string \| null` | **From the connection**, so it cannot be faked.  |

### What Gryt drops before you see it

| Limit | Value |
| --- | --- |
| Topic length | 64 characters |
| Payload size | 8192 bytes |
| Nesting depth | 8 |
| Values in a payload | 512 |

A `__proto__`, `constructor` or `prototype` key is refused as well, and so is
anybody sending more than thirty messages in ten seconds.
