Creating a plugin for Joomla

Writing a plugin for Joomla remains one of the most direct ways to extend the CMS without touching core files. Joomla's plugin architecture is event-driven: your code registers as a listener, waits for the system or another extension to fire a named event, and responds accordingly. The fundamentals haven't changed dramatically, but the shift toward PHP 8.2+ conventions, stricter typing, and Joomla's modern dispatcher system means the boilerplate you'll write today looks noticeably cleaner than it did even a couple of years ago.

TL:DR – A step-by-step guide to building a simple, installable Joomla 5 plugin in 2026. Get the structure right once and every plugin you write after becomes much faster to ship.

Step 1: Understand the Joomla Plugin Structure

Plugins live under /plugins/, organised by type. Common types include system, content, user, authentication, and behaviour. Each plugin directory contains at minimum:

  • XML manifest file — declares metadata, files, and configuration fields for the installer.
  • PHP file — holds the plugin class and its event-listener methods.
  • Optional additions — language files, helper classes, service providers, or assets.

One important note for 2026: Joomla 5 fully embraces the Symfony EventDispatcher under the hood. While the classic method-naming convention (e.g. onAfterInitialise) still works, the recommended pattern for new plugins is to use named event classes and type-hinted parameters. The examples below use the classic convention to keep things approachable, but be aware that the event-class approach is increasingly standard in the ecosystem and is worth adopting as your next step once the basics click.

Step 2: Create the Plugin Directory Structure

Create a directory for your plugin under /plugins/[plugin_type]/[plugin_name]/. For this walkthrough, we're building a system plugin called helloworld.

/plugins/system/helloworld/
    helloworld.php
    helloworld.xml
    language/
        en-GB.plg_system_helloworld.ini

Keeping the language folder inside the plugin directory (rather than under the global /language/ tree) is the current Joomla 5 convention and makes your plugin fully self-contained for packaging.

Step 3: Create the XML Manifest File

The manifest tells Joomla's installer everything it needs: what type of extension this is, which files to copy, and what configuration fields to expose in the backend. Create helloworld.xml:

<?xml version="1.0" encoding="utf-8"?>
<extension type="plugin" group="system" method="upgrade">
    <name>plg_system_helloworld</name>
    <author>Your Name</author>
    <creationDate>2026-01-01</creationDate>
    <copyright>Your Company</copyright>
    <license>GPL-2.0-or-later</license>
    <version>1.0.0</version>
    <description>PLG_SYSTEM_HELLOWORLD_DESC</description>

    <files>
        <filename plugin="helloworld">helloworld.php</filename>
    </files>

    <languages>
        <language tag="en-GB">language/en-GB.plg_system_helloworld.ini</language>
    </languages>

    <config>
        <fields name="params">
            <fieldset name="basic">
                <field
                    name="message"
                    type="text"
                    label="PLG_SYSTEM_HELLOWORLD_MESSAGE_LABEL"
                    description="PLG_SYSTEM_HELLOWORLD_MESSAGE_DESC"
                    default="Hello, Joomla 5!"
                />
            </fieldset>
        </fields>
    </config>
</extension>

Key elements:

  • The version attribute has been dropped from the <extension> tag itself — it was redundant and is no longer needed in Joomla 5 manifests.
  • <files> lists every file the installer should copy. Miss one here and Joomla won't deploy it.
  • <config> defines the fields shown on the plugin's settings screen. The <fields name="params"> wrapper tells Joomla to store these values as serialised parameters in the #__extensions database table.
  • Label and description values now reference language string keys rather than raw text — this is the correct approach even for single-language sites, since it keeps your manifest clean and translatable.

Step 4: Write the Plugin PHP File

Create helloworld.php. In 2026, Joomla 5 plugins should declare strict types and use fully qualified namespaces. The class extends CMSPlugin, which wires up the event dispatcher, autoloads language files, and gives you access to $this->params and $this->getApplication().

<?php

declare(strict_types=1);

defined('_JEXEC') or die;

use Joomla\CMS\Plugin\CMSPlugin;

class PlgSystemHelloworld extends CMSPlugin
{
    /**
     * Triggered after Joomla has finished initialising.
     *
     * @return void
     */
    public function onAfterInitialise(): void
    {
        // Retrieve the configurable message, falling back to a sensible default.
        $message = $this->params->get('message', 'Hello, Joomla 5!');

        // Enqueue the message so it appears in the system message area.
        $this->getApplication()->enqueueMessage($message);
    }
}

What's happening here:

  • declare(strict_types=1) is a PHP 8 best practice and aligns with Joomla's own core coding standards as of the 5.x series.
  • PlgSystemHelloworld extends CMSPlugin, giving you the full plugin toolkit without any manual wiring.
  • onAfterInitialise() is the event listener. Joomla calls it automatically after the system bootstraps. You can swap this for any other supported event — see the events reference in Step 7.
  • $this->getApplication() replaces the older Factory::getApplication() call. Accessing the application through the plugin's own method is cleaner and avoids a static dependency.
  • $this->params->get('message', 'Hello, Joomla 5!') reads the value the site administrator set in the plugin configuration, with a fallback if nothing has been saved.

Step 5: Add Language Files

Language files make your plugin translatable and keep human-readable strings out of your PHP and XML. Create language/en-GB.plg_system_helloworld.ini:

PLG_SYSTEM_HELLOWORLD="Hello World System Plugin"
PLG_SYSTEM_HELLOWORLD_DESC="A simple demonstration plugin for Joomla 5."
PLG_SYSTEM_HELLOWORLD_MESSAGE_LABEL="Display Message"
PLG_SYSTEM_HELLOWORLD_MESSAGE_DESC="The text shown to visitors after Joomla initialises."

Joomla will load this file automatically because the manifest declares it and the filename follows the en-GB.plg_[group]_[name].ini convention. If you later add a backend-specific strings file (suffixed .sys.ini), Joomla loads that in the administrator context — useful for keeping extension-manager labels separate from frontend strings.

Step 6: Install the Plugin in Joomla

  1. Package the plugin — Zip the contents of /plugins/system/helloworld/ so that helloworld.php, helloworld.xml, and the language/ folder are all at the root of the archive (not inside a subdirectory).

  2. Install via the Joomla backend:

    • Log into your Joomla administrator panel.
    • Navigate to System > Install > Extensions.
    • Use the Upload Package File tab and upload your .zip.
  3. Enable the plugin:

    • Go to System > Manage > Plugins.
    • Search for Hello World, then click the status toggle to enable it.
  4. Configure the plugin:

    • Click the plugin name to open its settings screen.
    • Update the Display Message field to whatever text you want shown, then save.

If you're working in a local development environment — which in 2026 typically means something like DDEV, Lando, or Laravel Herd — you can skip the zip-and-upload cycle entirely. Symlink your plugin directory directly into the Joomla /plugins/ tree and use the Discover function (System > Install > Discover) to register it. This makes iterative development significantly faster.

Step 7: Test the Plugin

With the plugin enabled, load any page of your Joomla site. Because the listener hooks into onAfterInitialise, the enqueued message will appear in the system messages area on both the frontend and the administrator panel. If you don't see it, check that the plugin is published and that your template renders the <jdoc:include type="message" /> tag — some custom templates omit it.

For more thorough testing, Joomla's built-in debug mode (Global Configuration > System > Debug System) surfaces query counts, memory usage, and loaded language strings, which is invaluable when your plugin starts doing heavier work. Pair that with a tool like Xdebug or Ray for step-debugging and you have a solid local development loop.

Common Events in Joomla 5 Plugins

Joomla's event system gives you fine-grained control over when your code runs. Here are the events you'll reach for most often:

  • onAfterInitialise — fires after the Joomla application bootstraps. Good for very early setup tasks.
  • onAfterRoute — fires once the router has resolved the current URL. Useful when you need to know which component is handling the request.
  • onBeforeRender — fires just before the template renders. A common hook for injecting assets or modifying the document object.
  • onAfterRender — fires after the full HTML response is assembled. Used for output manipulation.
  • onContentPrepare — fires when a content item is being prepared for display. The standard hook for content plugins that parse custom syntax or inject dynamic output.
  • onUserLogin / onUserLogout — fire during authentication events. Useful for session management, audit logging, or syncing with external systems.

Beyond these, Joomla 5 exposes a growing library of typed event classes — for example Joomla\CMS\Event\Content\ContentPrepareEvent — that carry strongly typed parameters rather than loose argument lists. If you're writing a plugin intended for distribution or long-term maintenance, adopting typed events now will make your code more robust and easier to update as Joomla evolves toward version 6.

Summary

Building a Joomla 5 plugin in 2026 comes down to four things: the right directory layout, a well-formed XML manifest, a PHP class that extends CMSPlugin and listens for the events you care about, and a language file that keeps your strings portable. The example above is intentionally minimal — but the same skeleton scales to anything from a simple content filter to a full payment gateway integration or headless API bridge.

Get comfortable with the event reference in the Joomla documentation, explore the typed event classes for production work, and lean on local tooling to keep your feedback loop tight. The plugin system is one of Joomla's genuine strengths: once you understand it, extending the CMS in almost any direction becomes a matter of finding the right event and writing the right response.