Developers

Plugins

A plugin is a class whose public methods are bound to event names. Something elsewhere triggers an event; every enabled plugin with a matching method runs, and what those methods return is collected and handed back to the caller. That is the entire mechanism. It is how the login form finds its authentication providers, how a group page grows a Forum tab, and how wiki markup becomes HTML.

Reach for a plugin when you want to react to something a component already does, and you do not want to edit that component to do it. The component triggers an event because it does not know, and should not know, what a particular hub wants to happen next. If instead the thing you are adding owns a URL and a screen of its own, you want a component; if it draws a box beside somebody else's page, you want a module. The table of the four kinds is the quick way to decide.

The example carried through this section

The rest of this book builds com_bookings, a component that books a lab's instruments. When it saves a new reservation it triggers one event of its own:

Event::trigger('bookings.onReservationCreate', array($reservation));

The component does not send mail. This lab wants the instrument's manager emailed whenever somebody books, so that behaviour goes in a plugin — plg_bookings_notify — which every chapter here builds a piece of. Another hub running the same component might answer the same event by writing to a shared calendar, or not install a plugin at all and get nothing. That choice belongs to whoever runs the hub, which is the point of triggering an event instead of sending the mail from the component.

The smallest working thing

Three files under app/plugins/bookings/notify/, and one command.

notify.php:

<?php
// No direct access
defined('_HZEXEC_') or die();

class plgBookingsNotify extends \Hubzero\Plugin\Plugin
{
	public function onReservationCreate($reservation)
	{
		$to = $this->params->get('manager_email');

		if (!$to)
		{
			return;
		}

		$message = new \Hubzero\Mail\Message();
		$message->setSubject('New reservation')
		        ->addFrom(Config::get('mailfrom'), Config::get('sitename'))
		        ->addTo($to)
		        ->addPart('A new reservation was created.', 'text/plain');

		$message->send();
	}
}

notify.xml — the manifest, which the Plugin Manager reads for the display name and the parameter form. migrations/Migration20260210000000PlgBookingsNotify.php:

<?php

use Hubzero\Content\Migration\Base;

defined('_HZEXEC_') or die();

class Migration20260210000000PlgBookingsNotify extends Base
{
	public function up()
	{
		$this->addPluginEntry('bookings', 'notify');
	}

	public function down()
	{
		$this->deletePluginEntry('bookings', 'notify');
	}
}

Then:

php core/bin/muse migration -f

Nothing else is declared anywhere. The method name onReservationCreate is the whole registration, and the row the migration wrote is what makes the loader find the file. The later chapters add the language file, the parameters, and a view for the mail body.

Groups

A plugin's group is the directory it sits in, and it is also the folder column of the plugin's extension row and the prefix on the events it hears. The group decides which events the plugin is loaded for and nothing else; it is not a type in any stronger sense. The groups shipped in core/plugins are:

answers         cron            members         tags
antispam        editors         metadata        tools
authentication  editors-xtd     newsletter      update
authfactors     extension       oaipmh          usage
blog            filesystem      projects        user
captcha         geocode         publications    whatsnew
cart            groups          resources       wiki
citation        handlers        search          xmessage
content         hubzero         support
courses         mail            system

Some are general: system plugins run on every request, content plugins transform text on its way to the page, user plugins react to account changes, cron plugins register scheduled jobs. Most of the rest belong to one component — groups, members, projects, resources, publications — and add a tab, a panel, or a step to it.

A hub adds its own group, as com_bookings does, simply by creating the directory and triggering events prefixed with its name. There is no register of groups to add to. Because of that there is also nothing to catch a typo: see Group and directory must match.

What a plugin is made of

core/plugins/members/blog/
    assets/css/blog.css
    assets/js/blog.js
    language/en-GB/en-GB.plg_members_blog.ini
    language/en-GB/en-GB.plg_members_blog.sys.ini
    migrations/Migration20170831000000PlgMembersBlog.php
    views/browse/tmpl/default.php
    blog.php
    blog.xml
    composer.json

Only blog.php and blog.xml are required, plus the migration if the plugin is ever to run. The class inside blog.php is plgMembersBlog, it extends Hubzero\Plugin\Plugin, and its public onMembersAreas() and onMembers() methods are what com_members triggers.

How a method becomes a listener

Hubzero\Plugin\Loader reads the enabled plugins out of #__extensions, requires each file, and constructs the class. Hubzero\Events\Dispatcher::addListener() then calls get_class_methods() on the instance and registers the object under every public method name it finds. When an event of that name is triggered, the method is called with the event's arguments.

Which events exist

The authoritative list is generated from the source tree: the events reference covers 279 events across 41 groups, and for each one names the call sites that fire it, the arguments they pass, and the plugins that already listen. Read the page for the group you are extending before you invent a method name — half the time the extension point you want is already there.

In this section

  • Migrations — registering the plugin. Read this first.
  • Structure — the directory layout and class naming.
  • Controllers — the plugin class, and the events it can answer.
  • Languages — translation files and autoloading.
  • Views — returning HTML from a plugin.
  • Assets — CSS, JavaScript, and images.
  • Configuration — parameters, global and per-object.
  • Packaging — the Composer and XML manifests, and how a plugin reaches a running hub.
  • Loading — importing groups, triggering events, and reading the responses.

Rewritten and checked against 2.4-main @ 91d03d0a23 on 2026-09-10.