Plugin System (Motherload)
The Motherload plugin framework provides a signal-based architecture for extending ticket system functionality without modifying core osTicket files.
Architecture
Plugins live in include/plugins/motherload/plugins/. The dispatcher (plugin.php) connects osTicket signals to plugin classes at runtime.
osTicket Action (e.g., thread entry created)
→ Signal dispatched (e.g., 'threadentry.created')
→ Motherload dispatcher checks registered plugins
→ Matching plugin's run() method executed
Plugin Structure
Each plugin is a PHP class that:
- Extends
motherloadPlugin - Declares a
signalConnectconstant matching an osTicket signal - Implements a
run(): boolmethod - Controls debug output via
$debug_ip
Minimal Plugin Example
<?php
/**
* Example plugin that logs ticket creation events.
*/
class plugin_ticket_logger extends motherloadPlugin
{
/**
* Signal this plugin listens to.
*/
public const signalConnect = 'ticket.created';
/**
* Debug output control.
* - true: show to everyone (dev only)
* - false: silent (production)
* - array: show to specific IPs only
*
* @var bool|array
*/
protected $debug_ip = false;
/**
* Execute plugin logic when signal fires.
*
* @return bool True on success, false on failure.
*/
public function run(): bool
{
$ticket = $this->getTicket();
$this->dbgecho('Ticket created: ' . $ticket->getNumber());
// Your custom logic here
return true;
}
}
Available Signals
| Signal | Triggered When |
|---|---|
threadentry.created | A new message, response, or note is posted |
ticket.created | A new ticket is created |
ticket.assigned | A ticket is assigned to staff |
ticket.transferred | A ticket is transferred between departments |
ticket.closed | A ticket is closed |
ticket.reopened | A closed ticket is reopened |
Debug Output Control
The $debug_ip property controls when dbgecho() output is displayed:
// Show debug output to everyone (development only)
protected $debug_ip = true;
// Silent — no debug output (production)
protected $debug_ip = false;
// Show only to specific IP addresses
protected $debug_ip = ['192.168.1.100', '10.0.0.50'];
Never set $debug_ip = true in production. Use false or a specific IP array for debugging deployed code.
Existing Plugins
| Plugin | Signal | Purpose |
|---|---|---|
plugin_ai_summary_update | threadentry.created | Queues ticket for Gemini AI analysis when a new entry is posted |
plugin_ai_summary_update
Location: include/plugins/motherload/plugins/ai_summary_update/plugin_ai_summary_update.php
Behavior:
- Fires on every new thread entry (message, response, or note)
- Checks if the ticket should be processed (filters out system events)
- Inserts a row into
ost_gemini_queuewithprocessed = 0 - Sets
bypass_cache: trueto force fresh AI analysis - The cron (
cron-gemini.php) picks up queued items for processing
Creating a New Plugin
- Create a directory:
include/plugins/motherload/plugins/{your_plugin}/ - Create the plugin file:
plugin_{your_plugin}.php - Define the class extending
motherloadPlugin - Set
signalConnectto the desired signal - Implement
run()with your logic - Set
$debug_ip = falsefor production
File Naming Convention
include/plugins/motherload/plugins/
{plugin_name}/
plugin_{plugin_name}.php ← Class: plugin_{plugin_name}
The dispatcher auto-discovers plugins by scanning the plugins directory.
Accessing Ticket Data in Plugins
The base motherloadPlugin class provides helper methods:
// Get the ticket object
$ticket = $this->getTicket();
// Get the thread entry that triggered the signal
$entry = $this->getEntry();
// Get entry type (M, R, or N)
$type = $entry->getType();
// Get the staff member (if applicable)
$staff = $this->getStaff();
Error Handling
Plugins should handle errors gracefully and return false on failure:
public function run(): bool
{
try {
$ticket = $this->getTicket();
if (!$ticket) {
$this->dbgecho('No ticket found');
return false;
}
// Plugin logic...
return true;
} catch (\Exception $exception) {
error_log('Plugin error: ' . $exception->getMessage());
return false;
}
}
A false return does not halt other plugins from executing — each plugin runs independently.