---
title: Plugins
description: Run code before execution
sidebar:
order: 4
---
:::tip
**TLDR:** Plugins are reusable pieces of code and are installable via `sern plugins`.
They can modify the module's fields and also perform preconditions.
Put them into the `plugins` field of a [module](/v4/reference/modules).
:::
## Installation
Chances are, you just want your bot to work. Plugins can preprocess and create reusable conditions for modules.
To install plugins, you can use the CLI:
```sh
sern plugins
```
:::tip
Feel free to contribute to the [repository](https://github.com/sern-handler/awesome-plugins)!
:::
:::caution
Some plugins only work with specific command types. Most, however, are targeted towards slash / both modules.
:::
import { Steps } from '@astrojs/starlight/components';
1. Install your favorite(s) (or the ones that look the coolest). I installed the `ownerOnly` plugin.
2. Thank the creator of the plugin. (mandatory)
3. Add the plugin to your module in the `plugins` field.
```ts title="src/commands/ping.ts" {6}
import { commandModule, CommandType } from '@sern/handler'
import { ownerOnly } from '../plugins'
export default commandModule({
type: CommandType.Both,
plugins: [ownerOnly(['182326315813306368'])],
description: 'ping command',
execute: (ctx) => {
ctx.reply('hello, owner');
}
})
```
┗|` O′|┛ perfect, your first plugin!
## Creating Plugins
Plugins are essentially functions that use the controller object to determine whether to continue or stop the execution of a command.
## Init Plugins
Init plugins modify how commands are loaded or do preprocessing.
```ts title="src/plugins/updateDescription.js" {3} {9}
import { CommandInitPlugin } from "@sern/handler";
export const updateDescription = (description: string) => {
return CommandInitPlugin(({ updateModule, deps }) => {
if(description.length > 100) {
deps.logger?.info({ message: "Invalid description" })
console.error("Description is invalid")
return controller.stop("From updateDescription: description is invalid");
}
updateModule({ description });
return controller.next(); // continue to next plugin
});
};
```
## Control Plugins

```js
import { CommandControlPlugin } from "@sern/handler";
export const inGuild = (guildId: string) => {
return CommandInitPlugin(ctx, sdt) => {
if(ctx.guild.id !== guildId) {
return controller.stop();
}
return controller.next();
});
};
```
1. An event is emitted by `discord.js`.
2. This event is passed to all control plugins **in order!!**,
3. If all are successful, the command is executed.
:::note
Calling `controller.stop()` notifies sern that this command should not be run, and command is ignored.
:::
:::tip
Control Plugins are good for filtering, preconditions, parsing.
:::
### Controller Object
The controller object is passed into every plugin. It has two methods: `next` and `stop`.
Plugins use the controller to control the flow of the command. For example, if a plugin fails, it calls `controller.stop()` to prevent the command from executing.
```ts
// Reference object, import this from @sern/handler
const controller = {
next: (val?: Record) => Ok(val),
stop: (val?: string) => Err(val),
};
```
### Passing state with SDT
> SDT = state, [dependencies](/v4/reference/dependencies), type (very creative)
Controllers can pass data downstream. That is, plugins can recieve data from previous plugin calls.
If all control plugins are successful, the final state is passed to the module `execute`.
```js
import { commandModule, CommandControlPlugin, CommandType } from '@sern/handler'
const plugin = CommandControlPlugin((ctx, sdt) => {
return controller.next({ a: 'from plugin1' });
});
const plugin2 = CommandControlPlugin((ctx, sdt) => {
return controller.next({ b: ctx.user.id + "from plugin2" });
})
export default commandModule({
type: CommandType.Slash,
plugins: [plugin, plugin2],
execute: (ctx, sdt) => {
console.log(sdt.state) // { a: 'from plugin1', b: '182326315813306368 from plugin2' }
}
})
```
:::tip
State passing is a glorified asynchronous [reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce).
:::
#### Caveats
Passing data with the same key will get overridden by the latest plugin.
It is recommended to namespace data keys if you have multiple plugins, or you can ensure no keys are overridden by the
plugin chain.
```js
return controller.next({ 'cheese-plugin/data' : "From cheese-plugin" })
```