Corsair
PluginsDiscord

Discord

Integrate Discord messages, threads, reactions, and interactions

Quick Start

Install the plugin:

pnpm install @corsair-dev/discord

Add the Discord plugin to your Corsair instance:

corsair.ts
import { createCorsair } from "corsair";
import { discord } from "@corsair-dev/discord";

export const corsair = createCorsair({
    plugins: [
        discord(),
    ],
});

Once configured, you can access the Discord API:

// Send a message to a channel
await corsair.discord.api.messages.send({
    channelId: "1234567890",
    content: "Hello from Corsair!",
});

Authentication

Supported Auth Types

The Discord plugin supports:

  • api_key (default) - Use a Discord bot token

Default Auth Type

If no authType is specified, the plugin defaults to api_key.

Configuring API Key Authentication

Store credentials with the CLI:

pnpm corsair setup --discord api_key=your-bot-token

For webhook signature verification, also store your app's Ed25519 public key:

pnpm corsair setup --discord public_key=your-public-key

The public key is found in the Discord Developer Portal under Application > General Information.

Alternatively, provide credentials directly in the config:

corsair.ts
discord({
    key: process.env.DISCORD_BOT_TOKEN,
    publicKey: process.env.DISCORD_PUBLIC_KEY,
})

See Authentication for details on managing credentials.

Options

The Discord plugin accepts the following configuration options:

OptionTypeDescription
authType'api_key'Authentication method (defaults to 'api_key')
keystringBot token (optional, uses database if not provided)
publicKeystringEd25519 public key for webhook signature verification
hooksobjectEndpoint hooks for custom logic
webhookHooksobjectWebhook hooks for event handling
errorHandlersobjectCustom error handlers
permissionsobjectPermission configuration for AI agent access

Hooks

discord({
    hooks: {
        messages: {
            send: {
                before: async (ctx, input) => {
                    console.log("Sending message to channel:", input.channelId);
                    return { ctx, input };
                },
            },
        },
    },
})

See Hooks for complete documentation.

Error Handling

The plugin includes built-in error handlers for common scenarios. For complete documentation, see the Error Handlers reference.

Usage

Accessing the API

// Send a message
await corsair.discord.api.messages.send({
    channelId: "1234567890",
    content: "Hello!",
});

// List channels in a guild
const channels = await corsair.discord.api.channels.list({
    guildId: "9876543210",
});

// Get guild members
const members = await corsair.discord.api.members.list({
    guildId: "9876543210",
});

See API Endpoints for the complete reference.

Webhooks

Discord sends interaction events (slash commands, buttons, modals) to your webhook URL:

app/api/webhook/route.ts
import { processWebhook } from "corsair";
import { corsair } from "@/server/corsair";

export async function POST(request: Request) {
    const headers = Object.fromEntries(request.headers);
    const body = await request.json();

    const result = await processWebhook(corsair, headers, body);
    return result.response;
}

Set your Discord app's Interactions Endpoint URL to https://your-domain.com/api/webhook.

See Webhooks for all available events.

Database Access

// Search synced guild data
const guilds = await corsair.discord.db.guilds.search({
    data: { name: "My Server" },
});

See Database for the complete schema.

Multi-Tenancy

const tenant = corsair.withTenant("user-123");

await tenant.discord.api.messages.send({
    channelId: "1234567890",
    content: "Hello from tenant user-123!",
});

Examples

Example 1: Respond to a Slash Command

corsair.ts
import { inngest } from "./inngest";

export const corsair = createCorsair({
    plugins: [
        discord({
            webhookHooks: {
                interactions: {
                    applicationCommand: {
                        after: async (ctx, result) => {
                            await inngest.send({ 
                                name: "discord/command-received", 
                                data: { 
                                    tenantId: ctx.tenantId, 
                                    commandName: result.data.data?.name, 
                                    userId: result.data.member?.user?.id, 
                                }, 
                            }); 
                        },
                    },
                },
            },
        }),
    ],
});
inngest/functions.ts
export const handleDiscordCommand = inngest.createFunction(
    { id: "handle-discord-command" },
    { event: "discord/command-received" },
    async ({ event }) => {
        const tenant = corsair.withTenant(event.data.tenantId);

        if (event.data.commandName === "ping") {
            await tenant.discord.api.messages.send({
                channelId: "1234567890",
                content: `Pong! Command from user ${event.data.userId}`,
            });
        }
    }
);

Example 2: Create a Thread on Important Messages

corsair.ts
export const corsair = createCorsair({
    plugins: [
        discord({
            webhookHooks: {
                interactions: {
                    messageComponent: {
                        after: async (ctx, result) => {
                            await inngest.send({
                                name: "discord/component-interaction",
                                data: {
                                    tenantId: ctx.tenantId,
                                    customId: result.data.data?.custom_id, 
                                    messageId: result.data.message?.id,
                                    channelId: result.data.channel_id,
                                },
                            });
                        },
                    },
                },
            },
        }),
    ],
});
inngest/functions.ts
export const handleComponentInteraction = inngest.createFunction(
    { id: "handle-component-interaction" },
    { event: "discord/component-interaction" },
    async ({ event }) => {
        const tenant = corsair.withTenant(event.data.tenantId);

        if (event.data.customId === "create-thread") {
            await tenant.discord.api.threads.createFromMessage({
                channelId: event.data.channelId,
                messageId: event.data.messageId,
                name: "Discussion Thread",
            });
        }
    }
);

Example 3: Post a Message with Reactions

inngest/functions.ts
export const postAnnouncement = inngest.createFunction(
    { id: "post-announcement" },
    { event: "app/announcement" },
    async ({ event }) => {
        const tenant = corsair.withTenant(event.data.tenantId);

        const message = await tenant.discord.api.messages.send({
            channelId: event.data.channelId,
            content: event.data.text, 
        });

        await tenant.discord.api.reactions.add({
            channelId: event.data.channelId,
            messageId: message.data.id,
            emoji: "👍", 
        });
    }
);