Custom Hooks (Plugins)
Multiforum supports a plugin system that allows extending functionality with custom code. Plugins can react to events and automate workflows.
Plugin System Overview
Plugins in Multiforum:
- Run in response to events (e.g., discussion created, comment posted)
- Are configured via pipelines
- Can have server-level or channel-level scope
- Support secrets for API keys and credentials
Plugin Architecture
┌─────────────────┐ Event ┌─────────────────┐
│ Multiforum │ ────────────▶ │ Plugin Engine │
│ (Backend) │ │ │
└─────────────────┘ └────────┬────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│Plugin A│ │Plugin B│ │Plugin C│
└────────┘ └────────┘ └────────┘
Events and Pipelines
Available Events
| Event | Description |
|---|---|
discussionChannel.created | New discussion posted |
comment.created | New comment posted |
event.created | New event created |
downloadableFile.created | New file uploaded |
Configuring Pipelines
Pipelines define which plugins run for each event:
Server-level (ServerConfig.pluginPipelines):
{
"discussionChannel.created": ["spam-detector", "auto-tagger"],
"comment.created": ["spam-detector"]
}
Channel-level (Channel.pluginPipelines):
{
"discussionChannel.created": ["channel-specific-plugin"]
}
Plugin Manifest
Plugins are defined with a manifest file:
{
"name": "my-plugin",
"version": "1.0.0",
"description": "Description of what the plugin does",
"author": "Your Name",
"events": ["discussionChannel.created", "comment.created"],
"secrets": [
{
"key": "API_KEY",
"description": "External service API key",
"scope": "server"
}
]
}
Manifest Fields
| Field | Required | Description |
|---|---|---|
name | Yes | Unique plugin identifier |
version | Yes | Semantic version |
description | Yes | What the plugin does |
author | No | Plugin author |
events | Yes | Events the plugin handles |
secrets | No | Required secrets configuration |
Plugin Secrets
Plugins can require secrets (API keys, tokens, etc.):
Secret Configuration
{
"secrets": [
{
"key": "OPENAI_API_KEY",
"description": "OpenAI API key for content analysis",
"scope": "server"
},
{
"key": "WEBHOOK_URL",
"description": "Webhook URL for notifications",
"scope": "channel"
}
]
}
Secret Scopes
| Scope | Description |
|---|---|
server | Single value for entire server |
channel | Can be different per channel |
Setting Secrets
Via the admin panel:
- Go to Admin Settings → Plugins
- Select the plugin
- Click Set Secret
- Enter the secret value
- Save
Secrets are encrypted at rest using PLUGIN_SECRET_ENCRYPTION_KEY.
Plugin Code Structure
// Example plugin handler
export async function handleDiscussionCreated(event, context) {
const { discussion, channel, author } = event;
const { secrets, ogm } = context;
// Access secrets
const apiKey = secrets.API_KEY;
// Perform plugin logic
const result = await analyzeContent(discussion.body, apiKey);
// Optionally return data or take actions
return { analysis: result };
}
Event Payload
interface DiscussionCreatedEvent {
discussion: {
id: string;
title: string;
body: string;
createdAt: Date;
};
channel: {
uniqueName: string;
displayName: string;
};
author: {
username: string;
};
}
Context Object
interface PluginContext {
secrets: Record<string, string>; // Plugin secrets
ogm: OGM; // Database access
serverName: string; // Current server
channelUniqueName?: string; // Channel (if applicable)
}
Installing Plugins
From Registry
- Add registry URL to
ServerConfig.pluginRegistries - Go to Admin Settings → Plugins
- Click Refresh Plugins
- Select a plugin
- Click Install
- Configure secrets if required
- Enable the plugin
Plugin Versioning
Plugins support versions:
- Install specific versions
- Update to newer versions
- Roll back if needed
Enabling Plugins
Server-Level
- Go to Admin Settings → Plugins
- Find the installed plugin
- Toggle Enabled
- Configure pipeline placement
Channel-Level
- Go to Channel Settings → Plugins
- Enable/disable plugins for this channel
- Channel plugins run in addition to server plugins
Plugin Execution
Viewing Plugin Runs
- Go to Admin Settings → Plugins
- Click on a plugin
- View Execution History
Each run shows:
- Event that triggered it
- Input data
- Output/result
- Duration
- Success/failure
Error Handling
If a plugin fails:
- Error is logged
- Main operation continues
- Admins can review failed runs
Example Use Cases
Spam Detection
export async function handleCommentCreated(event, context) {
const { comment } = event;
const isSpam = await checkForSpam(comment.text);
if (isSpam) {
// Create moderation issue
await context.ogm.model("Issue").create({
title: "Possible spam detected",
body: `Comment ${comment.id} flagged as potential spam`,
relatedCommentId: comment.id
});
}
}
Auto-Tagging
export async function handleDiscussionCreated(event, context) {
const { discussion } = event;
const suggestedTags = await analyzeTags(discussion.title, discussion.body);
// Could notify author or auto-apply tags
return { suggestedTags };
}
Webhook Notifications
export async function handleDiscussionCreated(event, context) {
const { discussion, channel, author } = event;
const webhookUrl = context.secrets.WEBHOOK_URL;
await fetch(webhookUrl, {
method: "POST",
body: JSON.stringify({
text: `New discussion in ${channel.displayName}: "${discussion.title}" by ${author.username}`
})
});
}
Best Practices
Plugin Development
- Keep plugins focused (single responsibility)
- Handle errors gracefully
- Log important actions
- Respect rate limits of external services
- Test thoroughly before deploying
Security
- Never log secrets
- Validate input data
- Use HTTPS for external calls
- Limit plugin permissions
Performance
- Avoid blocking operations
- Use async/await properly
- Consider timeouts
- Don't overload pipelines
Current Limitations
- Plugins run synchronously in pipelines
- Limited to defined events
- No UI for custom plugin development
- Plugin code must be hosted externally