Plugins are one of the key component of Tuleap. The code is structured around:

  • Core (everything under src/)

  • Plugins (located under plugins/pluginname)

Plugins can provide a new service (like AgileDashboard or Git) or underlying plumbing with almost no dedicated UI (like LDAP). A plugin can depend on another (Cardwall depends on Tracker).

Plugins rely on events to change behaviour of a given part of the code (either Core or another Plugin).

Unless a very good reason, all new significant development MUST be done within a plugin. It’s always true for new services.


A plugin folder is structured like

db            # Plugin tables and start data creation, uninstall & upgrade buckets
etc           # Configuration
include       # Plugin code
site-content  # Localisation strings
template      # mustache files
tests         # Unit & rest tests
www           # Entry point for web pages

As a good start, you can copy plugins/template and rename all template stuff to your plugin name.

There are a handful of files already there, the most important are:

  • db/install.sql definition of tables and initial data

  • db/uninstall.sql clean-up the base when the plugin is removed (purge)

  • include/templatePlugin.class.php the entry point that define the plugin behaviour

  • include/Template/Plugin/PluginDescription.php describe the plugin

  • include/Template/Plugin/PluginInfo.php plugin’s metadata (optionally access to the configuration values)

Note: if you copy the default plugin for “mercurial” plugin for instance, you will end up with:

  • mercurial/include/mercurialPlugin.class.php

  • mercurial/include/Mercurial/Plugin/PluginDescription.php

  • mercurial/include/Mercurial/Plugin/PluginInfo.php


This is the central place for plugin interaction with the rest of the application.

Let’s take a look at what our basic Mercurial plugin would look like

 5require_once __DIR__ . '/../vendor/autoload.php';
 7class mercurialPlugin extends Plugin
10    public function __construct($id)
11    {
12        parent::__construct($id);
13        $this->setScope(self::SCOPE_PROJECT);
14    }
16    public function getPluginInfo() : Tuleap\Mercurial\Plugin\PluginInfo
17    {
18        if (! $this->pluginInfo) {
19            $this->pluginInfo = new Tuleap\Mercurial\Plugin\PluginInfo($this);
20        }
21        return $this->pluginInfo;
22    }
24    public function getServiceShortname() : string
25    {
26        return 'plugin_mercurial';
27    }
29    public function process() : void
30    {
31        $renderer = TemplateRendererFactory::build()->getRenderer(MERCURIAL_BASE_DIR.'/template');
32        $renderer->renderToPage('index', array('world' => 'World'));
33    }

Line by line:

  • L5: code must not explicitly require PHP class definitions, everything should pass through the autoloader (automatically generated by Composer)

  • L11: define the scope to either SCOPE_SYSTEM (for system wide features like ldap, openidconnect, etc) or SCOPE_PROJECT for plugins that is relevant in the context of a project (here mercurial would be a service of the project)

  • L14 & L21: boilerplate to manage plugin description

  • L25: example of a basic controller (should only be done with very basic plugins).

Why process in the plugin class? It aims to encapsulate the controller with plugin information to only execute when plugin is activated. Example with the corresponding www/index.php:

$plugin_manager = PluginManager::instance();
$plugin         = $plugin_manager->getPluginByName('mercurial');

if ($plugin && $plugin_manager->isPluginAvailable($plugin)) {
} else {

Bring a new service to life

At this stage the plugin doesn’t do anything useful but it will display “Hello World” when the plugin is activated and someone reach the URL