# Plugins

Add your own services to a bot with plugins, which seedcord starts and stops with it. Covers attaching a plugin, reading it from a handler, and where a plugin can run.

Most bots need something beside Discord, like a database, a cache, or a client for another API. Wiring one up by hand means starting it before the bot logs in, handing it to every handler through a global or an import, and stopping it on shutdown before the process exits.

A plugin packages that work. It starts and stops at defined points in your bot's lifecycle, takes its own options, and logs on its own channel. You attach it under a key you pick, then read it from `this.core` anywhere `core` is available, like a handler.

```ts title="src/bot.ts"
import { resolve } from 'node:path';

import { Seedcord } from '@seedcord/gateway';
import { Mongoose } from '@seedcord/plugin-mongoose';
import { GatewayIntentBits } from 'discord.js';

export const seedcord = new Seedcord({
    bot: {
        clientOptions: { intents: [GatewayIntentBits.Guilds] },
        interactions: {
            path: resolve(import.meta.dirname, './handlers')
        },
        commands: {
            path: resolve(import.meta.dirname, './commands')
        },
        events: { path: null }
    },
    subscribers: { path: null }
}).attach('db', Mongoose, {
    dir: resolve(import.meta.dirname, './services'),
    uri: 'mongodb://localhost:27017/',
    name: 'seedcord'
});

export default seedcord;
```

[`attach`](https://docs.seedcord.org/packages/gateway/latest/seedcord#attach) takes the key, the plugin class, and whatever that class's constructor takes after the host. Here that's `'db'`, `Mongoose`, and its options object. If a plugin's constructor takes only the host, attach it with two arguments.

Keep the `export default`. `seedcord codegen` imports that default export to [type `this.core.db`](/plugins/typing). Without it, `this.core.db` doesn't compile.

## Reading it from a handler

`this.core` carries every attached plugin under its key.

```ts title="src/handlers/History.ts"
@SlashRoute('history')
export class History extends SlashHandler<'history'> {
    public async execute(): Promise<void> {
        const found =
            await this.core.db.services.users.findByName('ada');

        await this.reply(
            found ? `Found ${found.username}.` : 'No record.'
        );
    }
}
```

`History` reaches the mongoose service through `this.core.db`.

The sample declares its `Core` block by hand so it compiles on this page. In your project, `seedcord codegen` writes that block for you.

## Attaching more than one

A bot can attach more than one plugin, each under its own key. Chain another `attach` for each one. Inside one startup phase the plugins start one after another, in the order you attached them. If your plugin's `init()` reads another plugin, attach that one first. Otherwise your `init()` runs before the other plugin has started.

```ts
export const seedcord = new Seedcord({
    bot: {
        clientOptions: { intents: [GatewayIntentBits.Guilds] },
        interactions: {
            path: resolve(import.meta.dirname, './handlers')
        },
        commands: {
            path: resolve(import.meta.dirname, './commands')
        },
        events: { path: null }
    },
    subscribers: { path: null }
})
    .attach('db', Mongoose, {
        dir: resolve(import.meta.dirname, './mongo'),
        uri: 'mongodb://localhost:27017/',
        name: 'seedcord'
    })
    .attach('sql', KyselyPostgres, {
        dir: resolve(import.meta.dirname, './postgres'),
        connectionString: 'postgres://localhost:5432/seedcord',
        migrations: {
            path: resolve(import.meta.dirname, './migrations')
        }
    });
```

## The key also names a log channel

Every mongoose log line prints on the `db` channel, since a plugin logs on the channel named after its attach key. You can then filter or silence one plugin's logs by the key you gave it. seedcord already logs on reserved channels of its own, so those names can't be keys.

```ts
seedcord.attach('commands', Mongoose, {
    dir: resolve(import.meta.dirname, './services'),
    uri: 'mongodb://localhost:27017/',
    name: 'seedcord'
});
```

> **Warning**
>
> `commands` is one of those reserved channels, so that attach fails to compile. If the compiler can't see the key, like one read from an environment variable, `attach` throws `CorePluginReservedChannel` when it runs. [Logging](/tooling/logging) lists the reserved channels with what each one carries.

`attach` throws in two more cases when it runs:

* If the bot has already started, it throws `CorePluginAfterInit`.
* If another plugin or seedcord itself already uses the key on `core`, like `bus`, it throws `CorePluginKeyExists`.

## A plugin declares where it runs

Some plugins only work in one setup, like one that reads discord.js objects on gateway. A plugin can declare the transport it supports, `'gateway'` or `'http'`, and the runtime, `'server'` or `'edge'`. Both default to `'any'`, which attaches to any bot. If you attach a plugin to a bot that doesn't match, TypeScript reports the mismatch in your editor before the bot runs.

```txt output
this plugin declares transport 'gateway' but this bot runs 'http'
```

> **Http only**
>
> An edge bot can't use plugins. `createSeedcord` from `@seedcord/http/edge` returns a request handler, which doesn't have `attach`.

[Typing a plugin](/plugins/typing) explains the codegen step behind `this.core.db`. [The lifecycle](/plugins/lifecycle) says when a plugin starts and when it stops. To write one, start at [Writing your own](/plugins/your-own).
