Skip to main content

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:

  1. Extends motherloadPlugin
  2. Declares a signalConnect constant matching an osTicket signal
  3. Implements a run(): bool method
  4. 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

SignalTriggered When
threadentry.createdA new message, response, or note is posted
ticket.createdA new ticket is created
ticket.assignedA ticket is assigned to staff
ticket.transferredA ticket is transferred between departments
ticket.closedA ticket is closed
ticket.reopenedA 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'];
danger

Never set $debug_ip = true in production. Use false or a specific IP array for debugging deployed code.


Existing Plugins

PluginSignalPurpose
plugin_ai_summary_updatethreadentry.createdQueues 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:

  1. Fires on every new thread entry (message, response, or note)
  2. Checks if the ticket should be processed (filters out system events)
  3. Inserts a row into ost_gemini_queue with processed = 0
  4. Sets bypass_cache: true to force fresh AI analysis
  5. The cron (cron-gemini.php) picks up queued items for processing

Creating a New Plugin

  1. Create a directory: include/plugins/motherload/plugins/{your_plugin}/
  2. Create the plugin file: plugin_{your_plugin}.php
  3. Define the class extending motherloadPlugin
  4. Set signalConnect to the desired signal
  5. Implement run() with your logic
  6. Set $debug_ip = false for 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.