Plugin System

Plugin System

Understanding Plugins

What are Plugins?

Plugins are self-contained extensions that:

  • Extend Functionality - Add new features to your forum
  • Modify Behavior - Change how things work
  • Integrate Services - Connect with external APIs
  • Customize Experience - Tailor forum to your needs

Plugin Architecture

Plugins follow a structured architecture:

plugins/
└── my-plugin/
    ├── plugin.json          # Plugin metadata
    ├── MyPlugin.php         # Main plugin class
    ├── assets/              # CSS, JS, images
    │   ├── css/
    │   ├── js/
    │   └── img/
    ├── views/               # Template files
    │   └── admin.php
    └── README.md            # Documentation

Installing Plugins

  1. Download the plugin .zip from the Flatboard Resource Center or from the plugin author
  2. Go to Admin → Plugins
  3. Click "Install a plugin"
  4. Select the .zip archive and confirm — the archive is validated, extracted, and the plugin appears in the list automatically

The server validates the archive integrity, MIME type, size, and the presence of a valid plugin.json before extracting.

Method 2: Manual Installation (FTP / SSH)

Plugin packages are distributed as .zip archives. The manual workflow is: download → extract locally → upload the folder → activate.

Step 1: Download and Extract

  1. Download the plugin .zip from the Flatboard Resource Center or from the plugin author
  2. Verify that the plugin is compatible with your Flatboard 5 version
  3. Extract the archive on your local machine — you should get a folder (e.g. my-plugin/) with plugin.json at its root

Step 2: Upload the Plugin Folder

Via FTP

Connect to your server with an FTP client (FileZilla, Cyberduck, etc.) and upload the extracted plugin folder into the plugins/ directory of your Flatboard installation:

your-flatboard/
└── plugins/
    └── my-plugin/        ← upload this folder (not the .zip)
        ├── plugin.json
        └── ...
Via SSH

If you have shell access, you can extract the archive directly on the server:

cd /path/to/your-flatboard/plugins/
unzip my-plugin.zip          # extracts to my-plugin/
chmod 755 my-plugin/ -R      # adjust permissions if needed

Step 3: Activate the Plugin

Once the plugin folder is in place:

  1. Go to Admin → Plugins
  2. Find the plugin in the list — it will appear as inactive
  3. Click Activate

Step 4: Configure (if applicable)

If the plugin has settings, click the settings icon next to the plugin name, fill in the options, and save.

Creating a Plugin

Step 1: Create Plugin Structure

mkdir -p plugins/my-plugin/{assets/{css,js,img},views}

Step 2: Create plugin.json

{
  "id": "my-plugin",
  "name": "My Plugin",
  "version": "1.0.0",
  "description": "A custom plugin for Flatboard 5",
  "author": "Your Name",
  "license": "GPL3",
  "requires": {
    "flatboard": ">=5.0.0"
  },
  "class": "App\\Plugins\\MyPlugin\\MyPluginPlugin"
}

Step 3: Create Plugin Class

plugins/my-plugin/MyPluginPlugin.php:

<?php
namespace App\Plugins\MyPlugin;

use App\Core\Plugin;

class MyPluginPlugin
{
    public function boot()
    {
        // Register hooks
        Plugin::hook('view.header.content', [$this, 'addHeaderContent']);
        Plugin::hook('app.routes.register', [$this, 'registerRoutes']);
    }

    public function addHeaderContent(&$content)
    {
        $content .= '<link rel="stylesheet" href="proxy.php?url=/plugins/my-plugin/assets/css/style.css">';
    }

    public function registerRoutes($router)
    {
        $router->get('/my-plugin', function() {
            return 'Hello from my plugin!';
        });
    }
}

Step 4: Register Hooks

Flatboard 5 provides many hooks:

  • app.routes.register - Register custom routes
  • view.header.styles - Add CSS styles to header
  • view.footer.content - Add content to footer
  • view.post.avatar - Override post avatar rendering
  • view.user.avatar - Override user avatar rendering anywhere on the forum
  • discussion.created - When discussion is created
  • post.created - When post is created
  • user.registered - When user registers
  • user.updated - When user is updated
  • notification.types.register - Register custom notification types
  • visitor.page_info - Provide page metadata for presence indicator

See the complete hook list below for all available hooks.

Plugin Hooks

Available Hooks

Complete reference of all 74 hooks available in Flatboard 5. All data arguments marked by ref are passed by PHP reference — modifying them changes the actual value used by the framework.

Application

HookTrigger pointData
app.routes.registerApplication bootstrapRouter instance
router.plugins.registerRouter dispatchRouter instance
security.csp.directivesCSP header generationCSP directives array by ref

View — Layout

HookTrigger pointData
view.header.styles<head> (frontend + backend)Styles array by ref
view.footer.scripts</body> (frontend + backend)Scripts array by ref
view.footer.contentFooter (frontend)HTML content array by ref
view.footer.linksFooter links (frontend)Links array by ref
view.navbar.itemsMain navigation barNav items array by ref
view.main.beforeBefore main content (frontend)HTML string by ref
view.main.afterAfter main content (frontend)HTML string by ref
view.admin.sidebar.itemsAdmin sidebarSidebar items array by ref
view.admin.main.beforeBefore admin main contentHTML string by ref
view.admin.main.afterAfter admin main contentHTML string by ref

View — Banner

HookTrigger pointData
view.banner.dataBanner component renderBanner data array by ref
view.banner.contentBanner component renderHTML string by ref, banner data

View — SEO

HookTrigger pointData
seo.meta.before<head> meta tagsSEO data array by ref
seo.meta.afterAfter default meta tagsAdditional meta HTML string by ref

View — Auth Forms

HookTrigger pointData
view.login.formLogin page + login modalForm fields array by ref
view.login.validationLogin form submissionValidation error by ref
view.user.register.formRegistration page + modalForm fields array by ref
view.user.register.validationRegistration form submissionValidation error by ref

View — Discussions & Replies

HookTrigger pointData
view.discussion.create.formNew discussion formForm fields array by ref
view.discussion.create.validationDiscussion creation submissionValidation error by ref
view.discussion.show.before_contentDiscussion view (before first post)Event data array by ref
view.discussion.show.after_contentDiscussion view (after first post)Event data array by ref
view.reply.create.formReply formForm fields array by ref
view.reply.create.validationReply submissionValidation error by ref
view.sidebar.navigationDiscussion list sidebarSidebar items array by ref
view.post.avatarPost thread component — avatar render['user_id', 'username', 'avatar', 'size', 'html'] by ref — set html to override the default <img> output
view.user.avatarcomponents/avatar.php and discussion-item list (author + last-post author)['user_id', 'username', 'avatar', 'size', 'html'] by ref — set html to override the rendered avatar for any user across the forum

Content Events

HookTrigger pointData
discussion.createdAfter discussion savedEnriched event data array
discussion.created.legacyAfter discussion savedCreated discussion (legacy format)
discussion.deletedAfter discussion deletedDiscussion data
best_answer.setBest answer selected['discussion_id', 'post_id', 'user_id']
post.createdAfter reply savedCreated post data
post.updatedAfter reply editedUpdated post data
post.deletedAfter reply deletedPost data
category.createdAfter category savedCreated category data
category.updatedAfter category updatedUpdated category data
category.deletedAfter category deletedCategory data
tag.createdAfter tag savedTag data

User Events

HookTrigger pointData
user.registeredAfter registrationCreated user data
user.updatedAfter user profile savedUpdated user data
user.deletedAfter user account deletedUser data
ban.createdAfter ban appliedUpdated user data
ban.removedAfter ban liftedUpdated user data
user.profile.tabsProfile page renderTabs array by ref, user array, isOwnProfile bool
user.profile.tab.contentsProfile page renderTab contents array by ref, user array, isOwnProfile bool
user.settings.notificationsNotifications settings formOptions array by ref, preferences array
user.settings.notifications.saveNotifications settings savePreferences array by ref, request data, user
user.settings.preferences.saveGeneral preferences savePreferences array by ref, request data, user

Moderation

HookTrigger pointData
report.createdAfter content report savedCreated report data
HookTrigger pointData
search.resultsSearch service (all types)Results array by ref, query, type, keywords, filters
search.before.displayBefore HTML search resultsResults array by ref, query, type, filters
search.ajax.resultsBefore AJAX search resultsResults array by ref, query, type, filters

Notifications

HookTrigger pointData
notification.types.registerNotificationService::validateNotificationType() — called at boot time$allowedTypes array by ref — append custom type strings to extend the core whitelist
notification.before.createBefore notification writtenNotification data array by ref, type string
notification.after.createAfter notification writtenNotification ID, notification data
notification.email.bodyBefore notification email sentEmail body string by ref, notification data

Markdown & Editor

HookTrigger pointData
markdown.parseMarkdown rendering (all contexts)Result array by ref, raw markdown string
markdown.editor.configEditor component initConfig array by ref, $editorId
component.markdown-editor.beforeBefore editor textareaHTML string by ref, $editorId
component.markdown-editor.attributesEditor textarea tagAttributes array by ref, $editorId, config
markdown.editor.initEditor JS init scriptScript string by ref, $editorId, config
markdown.editor.getValueEditor getValue scriptScript string by ref, $editorId
markdown.editor.setValueEditor setValue scriptScript string by ref, $editorId, value path
component.markdown-editor.afterAfter editor textareaHTML string by ref, $editorId

Presence & Visitor Tracking

HookTrigger pointData
visitor.page_infoVisitor::getPageInfo() — unknown URLs only['page' => string, 'info' => array] by ref — set info['type'], info['title'], info['url']
visitor.before_trackVisitorTrackingMiddleware before write['ip_address', 'user_agent', 'page', 'user_id'] by ref
presence.anonymous_visitorsPresenceService::getActiveAnonymousVisitorsDetailed()Visitors array by ref
presence.botsPresenceService::getActiveBotsDetailed()Bots array by ref
presence.usersPresenceService::getActiveUsersDetailed()Users array by ref

Admin

HookTrigger pointData
admin.dashboard.widgetsAdmin dashboard renderWidgets array by ref

Plugin Views

HookTrigger pointData
plugin.view.indexPlugin index view rendered['pluginId', 'viewName', 'viewVars'] by ref
plugin.view.{viewName}Named plugin view rendered['pluginId', 'viewName', 'viewVars'] by ref
plugin.view.index.permissionsBefore plugin index viewPermissions array by ref, pluginId, 'index'
plugin.view.{viewName}.permissionsBefore named plugin viewPermissions array by ref, pluginId, viewName
plugin.view.{viewName}.varsBefore named plugin viewViewVars array by ref, pluginId, viewName
plugin.view.admin.varsBefore plugin admin viewViewVars array by ref, pluginId, 'admin'
plugin.view.privacy.varsBefore plugin privacy viewViewVars array by ref, pluginId, 'privacy'
plugin.view.cookies.varsBefore plugin cookies viewViewVars array by ref, pluginId, 'cookies'

Webhooks

HookTrigger pointData
webhook.receivedInbound webhook payloadWebhook data array

Using Hooks

// Register a hook
Plugin::hook('discussion.created', function($discussion) {
    // Do something when discussion is created
    Logger::info("New discussion: " . $discussion['title']);
});

// Register with priority
Plugin::hook('post.created', [$this, 'handlePostCreated'], 10);

// Extend the notification type whitelist (by reference)
Plugin::hook('notification.types.register', function(array &$types): void {
    $types[] = 'my_custom_type';
    $types[] = 'reputation_badge';
});

// Override avatar rendering for a virtual user (by reference)
Plugin::hook('view.user.avatar', function(array &$data): void {
    if ($data['user_id'] === 'my-bot') {
        $data['html'] = '<span class="avatar-bot"><i class="fas fa-robot"></i></span>';
    }
});

Plugin Configuration

Settings Storage

Plugin settings are stored in the "plugin" section of plugin.json and accessed via Plugin::getData(). Do not use Config::get/set for plugin-specific settings.

use App\Core\Plugin;

// Read a setting (with optional default)
$value = Plugin::getData('my-plugin', 'setting', 'default');

// Dot-notation is supported for nested keys
$host = Plugin::getData('my-plugin', 'smtp.host', '');

// Write a setting (in-memory only)
Plugin::setData('my-plugin', 'setting', 'value');

// Persist all settings to plugin.json
Plugin::saveData('my-plugin', ['setting' => 'value', 'another' => 'data']);

Admin Interface

Create admin settings page:

  1. Create view file: views/admin.php
  2. Register admin route
  3. Handle form submission
  4. Save settings to config

Best Practices

Plugin Development

  • Follow Structure - Use standard plugin structure
  • Namespace Properly - Use proper namespaces
  • Document Code - Add comments and documentation
  • Handle Errors - Implement error handling
  • Test Thoroughly - Test before release

Security

  • Validate Input - Always validate user input
  • Sanitize Output - Sanitize all output
  • Check Permissions - Verify user permissions
  • Use CSRF Protection - Protect forms with CSRF tokens

Performance

  • Lazy Load - Load assets only when needed
  • Cache When Possible - Cache expensive operations
  • Minimize Queries - Optimize database/file access
  • Use Hooks Efficiently - Don't overload hooks

Managing Plugins

Activating/Deactivating

  • Activate - Enable plugin functionality
  • Deactivate - Disable without uninstalling

Uninstalling a Plugin

Uninstalling permanently removes a plugin and all its data from your forum.

Using the Admin Panel

  1. Navigate to Admin → Plugins
  2. Deactivate the plugin first — the Uninstall button is blocked for active plugins
  3. Click the Uninstall button (trash icon, red/danger style) next to the plugin
  4. Confirm in the confirmation dialog that appears
  5. The server will:
    • Run the plugin's uninstall() hook (if defined), then deactivate() hook
    • Remove all plugin permissions
    • Clear asset and translation caches
    • Remove the plugin's entry from plugins.enabled
    • Recursively delete the plugin directory from plugins/

Restrictions

  • Core plugins (marked cantDisable = "1" in plugin.json) cannot be uninstalled — the button is hidden
  • Active plugins cannot be uninstalled — deactivate the plugin first

Implementing the Uninstall Hook

Plugins can run cleanup logic when uninstalled:

public function uninstall(): void
{
    // Clean up plugin-specific data in stockage/, custom tables, etc.
    // Called before the plugin directory is deleted
}

public function deactivate(): void
{
    // Called when plugin is deactivated (also called during uninstall)
}

Updating Plugins

  1. Backup - Backup your forum first
  2. Download - Get new plugin version
  3. Replace Files - Replace plugin files
  4. Clear Cache - Clear forum cache
  5. Test - Test plugin functionality

Plugin Dependencies

Some plugins require other plugins:

{
  "requires": {
    "flatboard": ">=5.0.0",
    "plugins": {
      "another-plugin": ">=1.0.0"
    }
  }
}

Troubleshooting

Plugin Not Loading

Check:

  1. Plugin structure is correct
  2. plugin.json is valid
  3. Plugin class exists and is correct
  4. Check error logs

Plugin Conflicts

Solution:

  1. Deactivate conflicting plugins
  2. Check hook priorities
  3. Review plugin code
  4. Contact plugin developers

Plugin Errors

Solution:

  1. Check error logs
  2. Enable debug mode (temporarily)
  3. Review plugin code
  4. Check Flatboard 5 compatibility

Resources

Last updated: March 10, 2026