The first plugin
An event is a moment in Joomla's work that it announces to anyone interested: "article text is being prepared for output", "a user logged in", "a record was saved". A plugin is a class subscribed to such events that runs its code when they happen. Let us write a plugin that replaces the {year} marker in articles with the current year.
Where it lives
Plugins are sorted into groups. A group is a folder inside plugins; a plugin of the content group handles content events. Create by hand:
plugins/content/hello/
├── hello.xml the manifest
├── services/
│ └── provider.php wires the plugin in
└── src/
└── Extension/
└── Hello.php the plugin classThe full name of such a plugin is plg_content_hello.
The manifest
<?xml version="1.0" encoding="UTF-8"?>
<!-- plugins/content/hello/hello.xml -->
<extension type="plugin" group="content" method="upgrade">
<name>plg_content_hello</name>
<version>1.0.0</version>
<description>Inserts the current year into article text</description>
<namespace path="src">My\Plugin\Content\Hello</namespace>
<files>
<folder plugin="hello">services</folder>
<folder>src</folder>
</files>
</extension>It differs from a module manifest by the plugin type, the group attribute and the plugin="hello" attribute on the first folder.
The provider
// plugins/content/hello/services/provider.php
defined('_JEXEC') or die;
use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use My\Plugin\Content\Hello\Extension\Hello;
return new class () implements ServiceProviderInterface {
public function register(Container $container): void
{
$container->set(
PluginInterface::class,
function (Container $container) {
$plugin = new Hello(
$container->get(DispatcherInterface::class),
(array) PluginHelper::getPlugin('content', 'hello'),
);
$plugin->setApplication(Factory::getApplication());
return $plugin;
},
);
}
};The provider creates the plugin object and hands it the event dispatcher, the settings and the application. In another plugin only the class, the group and the name change.
The plugin class
// plugins/content/hello/src/Extension/Hello.php
namespace My\Plugin\Content\Hello\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Event\Content\ContentPrepareEvent;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;
final class Hello extends CMSPlugin implements SubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
'onContentPrepare' => 'replaceYear',
];
}
public function replaceYear(ContentPrepareEvent $event): void
{
$article = $event->getItem();
if (empty($article->text)) {
return;
}
$article->text = str_replace('{year}', date('Y'), $article->text);
}
}The SubscriberInterface interface requires the getSubscribedEvents() method: it returns "event → method" pairs. The onContentPrepare event happens before text is output; the handler receives the event object, takes the article out of it and changes its text.
The ContentPrepareEvent class appeared in Joomla 5. In Joomla 4 the handler receives a generic Joomla\Event\Event object, and the article is in its arguments.
Installing and seeing the result
- System → Install → Discover → install
plg_content_hello. - System → Plugins → find the plugin and enable it: plugins are disabled after installation.
- Add the line
© {year} Anna Smithto an article and open the article on the site.
The current year is printed instead of the marker.
Plugin parameters
Settings are described in the manifest the same way as for a module:
<config>
<fields name="params">
<fieldset name="basic">
<field name="marker" type="text" label="Marker to replace" default="{year}" />
</fieldset>
</fields>
</config>In the plugin class they are available in the $this->params property:
$marker = $this->params->get('marker', '{year}');
$article->text = str_replace($marker, date('Y'), $article->text);The "Marker to replace" field appears in the plugin form in System → Plugins.
Common mistake
The onContentPrepare event fires not only for articles but also for modules, category descriptions and other texts — many times on one page. Slow code in the handler slows down the whole site. Check the context — $event->getContext() === 'com_content.article' — and leave the method at once if the text is not yours.