Skip to main content

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

EventDescription
discussionChannel.createdNew discussion posted
comment.createdNew comment posted
event.createdNew event created
downloadableFile.createdNew 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

FieldRequiredDescription
nameYesUnique plugin identifier
versionYesSemantic version
descriptionYesWhat the plugin does
authorNoPlugin author
eventsYesEvents the plugin handles
secretsNoRequired 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

ScopeDescription
serverSingle value for entire server
channelCan be different per channel

Setting Secrets

Via the admin panel:

  1. Go to Admin SettingsPlugins
  2. Select the plugin
  3. Click Set Secret
  4. Enter the secret value
  5. 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

  1. Add registry URL to ServerConfig.pluginRegistries
  2. Go to Admin SettingsPlugins
  3. Click Refresh Plugins
  4. Select a plugin
  5. Click Install
  6. Configure secrets if required
  7. Enable the plugin

Plugin Versioning

Plugins support versions:

  • Install specific versions
  • Update to newer versions
  • Roll back if needed

Enabling Plugins

Server-Level

  1. Go to Admin SettingsPlugins
  2. Find the installed plugin
  3. Toggle Enabled
  4. Configure pipeline placement

Channel-Level

  1. Go to Channel SettingsPlugins
  2. Enable/disable plugins for this channel
  3. Channel plugins run in addition to server plugins

Plugin Execution

Viewing Plugin Runs

  1. Go to Admin SettingsPlugins
  2. Click on a plugin
  3. 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