Documentation Index
Fetch the complete documentation index at: https://mintlify.com/itsmng/itsm-ng/llms.txt
Use this file to discover all available pages before exploring further.
This comprehensive guide walks you through creating custom plugins for ITSM-NG, from basic structure to advanced integration.
Getting Started
Prerequisites
Before developing a plugin, you should have:
- Understanding of PHP and object-oriented programming
- Familiarity with ITSM-NG architecture and database structure
- Knowledge of HTML, CSS, and JavaScript for UI components
- A development environment with ITSM-NG installed
- Access to ITSM-NG source code for reference
Development Environment Setup
Create Plugin Directory
cd /var/www/html/itsm-ng/plugins/
mkdir myplugin
cd myplugin
Set Development Permissions
chown -R $USER:www-data /var/www/html/itsm-ng/plugins/myplugin
chmod -R 775 /var/www/html/itsm-ng/plugins/myplugin
Enable Error Reporting
In your ITSM-NG config, enable debugging during development to catch errors early.
Basic Plugin Structure
Create the following directory structure:
plugins/myplugin/
├── setup.php # Required: Plugin metadata and initialization
├── hook.php # Optional: Hook implementations
├── README.md # Documentation
├── LICENSE # License file (GPLv2+ recommended)
├── locales/ # Internationalization
│ ├── en_GB.mo
│ ├── en_GB.po
│ ├── fr_FR.mo
│ └── fr_FR.po
├── inc/ # PHP classes
│ ├── config.class.php
│ ├── item.class.php
│ └── profile.class.php
├── front/ # User interface pages
│ ├── config.php
│ ├── item.php
│ └── item.form.php
├── ajax/ # AJAX handlers
│ └── handler.php
├── css/ # Stylesheets
│ └── styles.css
├── js/ # JavaScript files
│ └── scripts.js
├── pics/ # Images and icons
│ └── icon.png
└── sql/ # Database scripts
├── install.sql
└── uninstall.sql
Creating setup.php
The setup.php file is required and contains plugin metadata and initialization:
<?php
/**
* Plugin version information
*/
function plugin_version_myplugin() {
return [
'name' => 'My Plugin',
'version' => '1.0.0',
'author' => 'Your Name',
'license' => 'GPLv2+',
'homepage' => 'https://github.com/yourusername/myplugin',
'requirements' => [
'glpi' => [
'min' => '10.0.0',
'max' => '11.0.0'
],
'php' => [
'min' => '7.4.0',
'exts' => [
'curl',
'json',
'mysqli'
]
]
]
];
}
/**
* Check plugin prerequisites
*/
function plugin_myplugin_check_prerequisites() {
// Check GLPI version
if (version_compare(GLPI_VERSION, '10.0.0', 'lt')) {
echo "This plugin requires GLPI >= 10.0.0";
return false;
}
// Check required extensions
if (!extension_loaded('curl')) {
echo "This plugin requires PHP curl extension";
return false;
}
return true;
}
/**
* Check plugin configuration
*/
function plugin_myplugin_check_config() {
// Return true if plugin is configured
// Return false if configuration is required
return true;
}
/**
* Initialize plugin
*/
function plugin_init_myplugin() {
global $PLUGIN_HOOKS;
// Register plugin
$PLUGIN_HOOKS['csrf_compliant']['myplugin'] = true;
// Initialize plugin classes
Plugin::registerClass('PluginMypluginItem', [
'document_types' => true,
'ticket_types' => true,
'addtabon' => ['Computer', 'Ticket']
]);
// Add menu entry
$PLUGIN_HOOKS['menu_toadd']['myplugin'] = ['tools' => 'PluginMypluginMenu'];
// Add to config menu
$PLUGIN_HOOKS['config_page']['myplugin'] = 'front/config.php';
// Add CSS and JS
$PLUGIN_HOOKS['add_css']['myplugin'] = 'css/styles.css';
$PLUGIN_HOOKS['add_javascript']['myplugin'] = 'js/scripts.js';
}
Source: inc/plugin.class.php:1521-1538
Installation and Uninstallation
Install Function
Create database tables and initialize plugin data:
/**
* Install plugin
*/
function plugin_myplugin_install() {
global $DB;
// Check if already installed
if (TableExists('glpi_plugin_myplugin_items')) {
return true;
}
// Create database tables
$query = "CREATE TABLE IF NOT EXISTS `glpi_plugin_myplugin_items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`entities_id` int(11) NOT NULL DEFAULT '0',
`is_recursive` tinyint(1) NOT NULL DEFAULT '0',
`date_creation` timestamp NULL DEFAULT NULL,
`date_mod` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `name` (`name`),
KEY `entities_id` (`entities_id`),
KEY `is_recursive` (`is_recursive`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
$DB->queryOrDie($query, $DB->error());
// Create config table
$query = "CREATE TABLE IF NOT EXISTS `glpi_plugin_myplugin_configs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`api_key` varchar(255) DEFAULT NULL,
`api_url` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
$DB->queryOrDie($query, $DB->error());
// Insert default configuration
$query = "INSERT INTO `glpi_plugin_myplugin_configs`
(`id`, `api_key`, `api_url`)
VALUES (1, '', '')";
$DB->queryOrDie($query, $DB->error());
return true;
}
Source: inc/plugin.class.php:724-784
Uninstall Function
Remove all plugin data:
/**
* Uninstall plugin
*/
function plugin_myplugin_uninstall() {
global $DB;
// Drop all plugin tables
$tables = [
'glpi_plugin_myplugin_items',
'glpi_plugin_myplugin_configs'
];
foreach ($tables as $table) {
$query = "DROP TABLE IF EXISTS `$table`";
$DB->queryOrDie($query, $DB->error());
}
// Remove plugin-specific rights from profiles
$query = "DELETE FROM `glpi_profilerights`
WHERE `name` LIKE 'plugin_myplugin_%'";
$DB->queryOrDie($query, $DB->error());
// Clean up cron tasks
CronTask::Unregister('myplugin');
return true;
}
Source: inc/plugin.class.php:670-711
Creating Plugin Classes
Basic Item Class
Create inc/item.class.php:
<?php
class PluginMypluginItem extends CommonDBTM {
// Right management
static $rightname = 'plugin_myplugin_item';
/**
* Get type name
*/
static function getTypeName($nb = 0) {
return __('My Plugin Item', 'myplugin');
}
/**
* Get menu name
*/
static function getMenuName() {
return static::getTypeName(Session::getPluralNumber());
}
/**
* Show form
*/
function showForm($ID, $options = []) {
if (!$this->canView()) {
return false;
}
if ($ID > 0) {
$this->check($ID, READ);
} else {
$this->check(-1, CREATE);
}
$this->showFormHeader($options);
echo "<tr class='tab_bg_1'>";
echo "<td>" . __('Name') . "</td>";
echo "<td>";
Html::autocompletionTextField($this, 'name');
echo "</td>";
echo "</tr>";
$this->showFormButtons($options);
return true;
}
/**
* Get search options
*/
function rawSearchOptions() {
$tab = [];
$tab[] = [
'id' => 'common',
'name' => self::getTypeName()
];
$tab[] = [
'id' => '1',
'table' => $this->getTable(),
'field' => 'name',
'name' => __('Name'),
'datatype' => 'itemlink',
'massiveaction' => false
];
$tab[] = [
'id' => '2',
'table' => $this->getTable(),
'field' => 'id',
'name' => __('ID'),
'massiveaction' => false,
'datatype' => 'number'
];
return $tab;
}
}
Source: inc/plugin.class.php:1307-1372
Configuration Class
Create inc/config.class.php:
<?php
class PluginMypluginConfig extends CommonDBTM {
static $rightname = 'config';
/**
* Get configuration
*/
static function getConfig() {
global $DB;
$config = new self();
$config->getFromDB(1);
return $config->fields;
}
/**
* Update configuration
*/
static function setConfig($data) {
$config = new self();
return $config->update(array_merge(['id' => 1], $data));
}
/**
* Show configuration form
*/
function showConfigForm() {
if (!Config::canUpdate()) {
return false;
}
$config = self::getConfig();
echo "<form method='post' action='" . $this->getFormURL() . "'>";
echo "<div class='center' id='tabsbody'>";
echo "<table class='tab_cadre_fixe'>";
echo "<tr><th colspan='2'>" . __('Plugin Configuration', 'myplugin') . "</th></tr>";
echo "<tr class='tab_bg_1'>";
echo "<td>" . __('API Key', 'myplugin') . "</td>";
echo "<td>";
echo "<input type='text' name='api_key' value='" . $config['api_key'] . "' size='50'>";
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_1'>";
echo "<td>" . __('API URL', 'myplugin') . "</td>";
echo "<td>";
echo "<input type='text' name='api_url' value='" . $config['api_url'] . "' size='50'>";
echo "</td>";
echo "</tr>";
echo "<tr class='tab_bg_2'>";
echo "<td class='center' colspan='2'>";
echo "<input type='submit' name='update' value='" . __('Save') . "' class='submit'>";
echo "</td>";
echo "</tr>";
echo "</table></div>";
Html::closeForm();
return true;
}
}
Implementing Hooks
Create hook.php to handle various ITSM-NG hooks:
<?php
/**
* Display hook on item form
*/
function plugin_myplugin_item_add_form(CommonDBTM $item) {
if ($item instanceof Computer) {
echo "<tr class='tab_bg_1'>";
echo "<th>" . __('My Plugin Info', 'myplugin') . "</th>";
echo "<td>Custom content for computers</td>";
echo "</tr>";
}
}
/**
* Hook called when item is added
*/
function plugin_myplugin_item_add(CommonDBTM $item) {
if ($item instanceof Computer) {
// Perform action when computer is created
error_log("Computer created: " . $item->fields['name']);
}
}
/**
* Hook called when item is updated
*/
function plugin_myplugin_item_update(CommonDBTM $item) {
if ($item instanceof Computer) {
// Perform action when computer is updated
error_log("Computer updated: " . $item->fields['name']);
}
}
/**
* Hook called when item is deleted
*/
function plugin_myplugin_item_purge(CommonDBTM $item) {
if ($item instanceof Computer) {
// Perform cleanup when computer is deleted
error_log("Computer deleted: " . $item->fields['name']);
}
}
/**
* Add custom search options
*/
function plugin_myplugin_getAddSearchOptionsNew($itemtype) {
$options = [];
if ($itemtype == 'Computer') {
$options[] = [
'id' => '9000',
'table' => 'glpi_plugin_myplugin_items',
'field' => 'custom_field',
'name' => __('Custom Field', 'myplugin'),
'datatype' => 'text'
];
}
return $options;
}
/**
* Register database relations
*/
function plugin_myplugin_getDatabaseRelations() {
return [
'glpi_computers' => [
'glpi_plugin_myplugin_items' => 'computers_id'
],
'glpi_entities' => [
'glpi_plugin_myplugin_items' => 'entities_id'
]
];
}
/**
* Register dropdowns
*/
function plugin_myplugin_getDropdown() {
return [
'PluginMypluginCategory' => __('My Plugin Category', 'myplugin'),
'PluginMypluginType' => __('My Plugin Type', 'myplugin')
];
}
Source: inc/plugin.class.php:1383-1427, 1650-1680, 1583-1595
Register your plugin in the menu system:
function plugin_init_myplugin() {
global $PLUGIN_HOOKS;
// Add to Tools menu
$PLUGIN_HOOKS['menu_toadd']['myplugin'] = [
'tools' => 'PluginMypluginMenu'
];
// Add to Config menu
$PLUGIN_HOOKS['menu_toadd']['myplugin'] = [
'config' => 'PluginMypluginMenu'
];
}
class PluginMypluginMenu extends CommonGLPI {
static function getMenuName() {
return __('My Plugin', 'myplugin');
}
static function getMenuContent() {
$menu = [];
$menu['title'] = self::getMenuName();
$menu['page'] = '/plugins/myplugin/front/item.php';
$menu['icon'] = 'fas fa-puzzle-piece';
$menu['options']['item'] = [
'title' => PluginMypluginItem::getTypeName(2),
'page' => '/plugins/myplugin/front/item.php',
'icon' => 'fas fa-list'
];
$menu['options']['config'] = [
'title' => __('Configuration'),
'page' => '/plugins/myplugin/front/config.php',
'icon' => 'fas fa-cog'
];
return $menu;
}
}
Internationalization
Create translation files in the locales/ directory:
Creating .pot Template
cd /var/www/html/itsm-ng/plugins/myplugin
xgettext -o locales/myplugin.pot --language=PHP \
--keyword=__ --keyword=_n:1,2 --keyword=_x:1c,2 \
--from-code=UTF-8 *.php inc/*.php front/*.php
Creating Language Files
- Copy
myplugin.pot to locales/en_GB.po
- Translate strings in the .po file
- Compile to .mo format:
msgfmt locales/en_GB.po -o locales/en_GB.mo
Using Translations
// Simple translation
echo __('Hello World', 'myplugin');
// Plural forms
echo _n('One item', '%d items', $count, 'myplugin');
// Context-specific translation
echo _x('button', 'Save', 'myplugin');
Source: inc/plugin.class.php:288-378
Best Practices
Naming Conventions
- Plugin directory: Lowercase, no spaces (e.g.,
myplugin)
- Classes:
PluginMypluginClassname (CamelCase with plugin prefix)
- Functions:
plugin_myplugin_function_name (lowercase with underscores)
- Database tables:
glpi_plugin_myplugin_tablename (lowercase with underscores)
- Rights:
plugin_myplugin_right (lowercase with underscores)
Security
// Always sanitize user input
$name = $DB->escape($_POST['name']);
// Use prepared statements
$query = "SELECT * FROM glpi_plugin_myplugin_items WHERE id = ?";
$result = $DB->query($query, [$id]);
// Check permissions
if (!$item->canView()) {
Html::displayRightError();
return;
}
// CSRF protection (automatically enabled)
$PLUGIN_HOOKS['csrf_compliant']['myplugin'] = true;
Source: inc/plugin.class.php:803-813
- Lazy loading: Only load classes when needed
- Caching: Use ITSM-NG’s cache system for expensive operations
- Database indexing: Add indexes to frequently queried columns
- Minimize hooks: Only implement hooks you actually need
Error Handling
try {
$result = $DB->query($query);
if (!$result) {
throw new Exception('Database query failed');
}
} catch (Exception $e) {
Toolbox::logError($e->getMessage());
Session::addMessageAfterRedirect(
__('An error occurred', 'myplugin'),
false,
ERROR
);
}
Testing Your Plugin
Install in Development
Install the plugin in your development ITSM-NG instance.
Test Installation Process
Verify database tables are created correctly and default data is inserted.
Test Activation
Ensure the plugin activates without errors and appears in menus.
Test Functionality
Verify all features work as expected, including forms, searches, and hooks.
Test Deactivation
Confirm the plugin deactivates cleanly without breaking ITSM-NG.
Test Uninstallation
Verify all data is removed and no orphaned records remain.
Test Updates
Test upgrading from one version to another with database migrations.
Debugging
Enable debugging during development:
// In your plugin code
error_log("Debug info: " . print_r($data, true));
// Use GLPI's debugging
Toolbox::logDebug("Debug message");
Toolbox::logError("Error message");
Toolbox::logWarning("Warning message");
// Check SQL queries
$DB->query($query) or die($DB->error());
Distribution
Preparing for Release
- Update version in
setup.php
- Create changelog documenting changes
- Update README with installation instructions
- Test thoroughly in a clean ITSM-NG installation
- Check compatibility with target ITSM-NG versions
Packaging
# Create release archive
cd /var/www/html/itsm-ng/plugins/
tar -czf myplugin-1.0.0.tar.gz myplugin/ \
--exclude='myplugin/.git' \
--exclude='myplugin/node_modules' \
--exclude='myplugin/*.log'
Publishing
- Create a GitHub repository
- Tag releases with version numbers
- Submit to GLPI/ITSM-NG plugin directory
- Provide documentation and support channels
Next Steps