Table of Contents
Modules allow the display of relatively small snippets of information in the front– or backend of a Joomla! site.
Modules are the oldest extension type in Joomla. It was the first ever extension type added to Mambo, Joomla's predecessor, back in the early 2000s.
Modules had remain largely unchanged ever since, with the exception of a few additions (like support for view templates) over the course of Joomla! 1.5. It should be no surprise that modules were thoroughly rethought in Joomla 4. After all, a 20-year-old concept does need a big kick to bring it up to speed with modern development standards.
In its purest and most traditional form, a module consists of an
XML manifest file and a PHP file which outputs content, plus the
language file. Let's take a minimal site module as an example. The
manifest file example.xml
looks like this:
<?xml version="1.0" encoding="utf-8"?> <extension type="module" method="upgrade" client="site"> <name>MOD_EXAMPLE</name> <author>Acme Corp.</author> <creationDate>2023-08-16</creationDate> <copyright>Copyright (c)2023 Acme Cord</copyright> <license>GNU GPL v3 or later</license> <authorEmail>acme@example.com</authorEmail> <authorUrl>www.example.com</authorUrl> <version>1.0.0</version> <description>MOD_EXAMPLE_XML_DESC</description> <files> <file module="mod_example">mod_example.php</file> </files> <languages folder="language"> <language tag="en-GB">en-GB/en-GB.mod_example.sys.ini</language> </languages> <config addfieldpath="/administrator/components/com_ats/fields"> <fields name="params"> <fieldset name="advanced"> <field name="moduleclass_sfx" type="textarea" rows="3" label="COM_MODULES_FIELD_MODULECLASS_SFX_LABEL" description="COM_MODULES_FIELD_MODULECLASS_SFX_DESC" /> <field name="cache" type="list" default="1" label="COM_MODULES_FIELD_CACHING_LABEL" description="COM_MODULES_FIELD_CACHING_DESC" > <option value="1">JGLOBAL_USE_GLOBAL</option> <option value="0">COM_MODULES_FIELD_VALUE_NOCACHING</option> </field> <field name="cache_time" type="text" default="900" label="COM_MODULES_FIELD_CACHE_TIME_LABEL" description="COM_MODULES_FIELD_CACHE_TIME_DESC" /> <field name="cachemode" type="hidden" default="static"> <option value="static"></option> </field> </fieldset> </fields> </config> </extension>
The module configuration you see above is pretty much boilerplate. It lets com_modules (the built-in component which manages modules) to display standard module controls such as the module class suffix, and caching options[12].
The module file, mod_example.php
, is dead
simple in its simplest form:
<?php defined('_JEXEC') || die; ?> Hello, world!
Adding view templates
Of course, modules are rarely this simple. They probably need to do some kind of processing e.g. talk to a component to get some data to display. While you could intermix display and logic in the module file —as was the case in the olden days of Joomla 1.x/2.x— this causes a pretty obvious problem for site integrators. They cannot override the module's display. For this reason, the concept of view templates was introduced in modules during the Joomla! 1.5 lifecycle. This requires two changes.
First, we need to add one more option, layout
, to the
advanced fieldset of the XML manifest:
<field name="layout" type="modulelayout" label="JFIELD_ALT_LAYOUT_LABEL" description="JFIELD_ALT_MODULE_LAYOUT_DESC" validate="moduleLayout" />
Then, we need to change our module file to use
\Joomla\CMS\Helper\ModuleHelper::getLayoutPath
to get the filesystem path of the view template to load:
<?php defined('_JEXEC') || die; ?> require_once \Joomla\CMS\Helper\ModuleHelper::getLayoutPath('mod_example', $params->get('layout', 'default');
The view templates are loaded from your module's
tmpl
directory. In the example above, the default
view template is default.php
.
If you're wondering where $params
came from: this is
a \Joomla\Registry\Registry
object holding your
module's configuration data. It is automatically passed by Joomla! to
your module.
Since we added a folder, we need to add it to the XML manifest as well:
<folder>tmpl</folder>
Adding a Helper
Using a view template is an indubitably better approach than mixing logic and presentation code in the module. However, it still makes for a really miserable developer experience since you have raw code running inside a context which is not under your control. The function and variable names you use might end up conflicting with those added in future versions of Joomla!, leading to hilarious and hard to troubleshoot problems with your plugin.
This is why the concept of module helpers was introduced to Joomla. In classic modules this is just an abstract class with static methods you include from your module code. All your executable code is put there. Look at any core module in Joomla! 3.10 to see how it's done.
Even with view templates and helpers, the classic modules are still a bit of a mess from a developer's point of view. You still have that ugly module entry point file which executes under an arbitrary context. There is still a lot of room for bugs because of function and variable names conflicts. Moreover, you can't make use of any service providers which, as we have discussed, are the lifeblood of Joomla's architecture from Joomla 4 onwards.
To this end, modules were rethought in Joomla! 4.
There is no longer a module entry point file. Instead, you have a
Dispatcher class. You override the Dispatcher's
getLayoutData
method to return an array with
data that can be displayed by your module's view templates. This is a
complete replacement to the module entry point file. Since it's a class
instantiated by Joomla, the execution is isolated in its own context,
therefore preventing any issues with function and variable
naming.
The Dispatcher automatically loads the correct view template file
and passes it the data returned from
getLayoutData
as variables
(it runs extract()
on the array of data).
![]() | Important |
---|---|
This is the big change from Joomla 3.x and earlier. In older versions of Joomla! you could have your module's view template pull data from the module's helper by calling the helper's static methods. This is no longer the case. In Joomla! 4 and later it is the Dispatcher which calls the (non-static!) methods of the helper object. Then, it pushes the data to the view template. Once you understand the difference between pulling data and pushing data the whole concept of modules under Joomla 4 and later becomes crystal clear. |
The presence of a Helper class is assumed. All you have to do is tell Joomla what it's called in your module's service provider file.
![]() | Important |
---|---|
Unlike Joomla 3.x and earlier, the module's helper no longer has static methods. It has regular, non-static methods. When you need to use the helper in your Dispatcher you will be given an instantiated object. This is deliberate. You cannot push services, like the database object, to a class (at least not without violating several good software design principles). You can trivially do so in objects which implement some of the handy Interfaces and use some of the handy Traits provided by Joomla. We'll see how that works a bit later. |
Of course, all executable PHP code is namespaced — just like with all Joomla 4 and later native extensions.
This may sound like a lot of work, but it's really not that bad. In the past you'd create the XML manifest, the language file, your module entry point, your helper, and your view template, in this order. With Joomla 4 and alter you create the XML manifest, the language file, the service locator, the dispatcher, your helper, and your view template, in this order. The service locator is just boilerplate. Essentially, you are replacing the module entry point with a bit of boilerplace (service locator) and an overridden method in your Dispatcher. Everything else is the same. It only sounds hard because it's different, not because it is any harder.
So, here's our XML manifest file:
<?xml version="1.0" encoding="utf-8"?> <extension type="module" method="upgrade" client="site"> <name>MOD_EXAMPLE</name> <author>Acme Corp.</author> <creationDate>2023-08-16</creationDate> <copyright>Copyright (c)2023 Acme Cord</copyright> <license>GNU GPL v3 or later</license> <authorEmail>acme@example.com</authorEmail> <authorUrl>www.example.com</authorUrl> <version>1.0.0</version> <description>MOD_EXAMPLE_XML_DESC</description> <namespace path="src">Acme\Module\Example</namespace> <files> <folder module="mod_example">services</folder> <folder>src</folder> <folder>tmpl</folder> </files> <languages folder="language"> <language tag="en-GB">en-GB/en-GB.mod_example.sys.ini</language> </languages> <config addfieldpath="/administrator/components/com_ats/fields"> <fields name="params"> <fieldset name="advanced"> <field name="layout" type="modulelayout" label="JFIELD_ALT_LAYOUT_LABEL" class="form-select" validate="moduleLayout" /> <field name="moduleclass_sfx" type="textarea" rows="3" label="COM_MODULES_FIELD_MODULECLASS_SFX_LABEL" description="COM_MODULES_FIELD_MODULECLASS_SFX_DESC" /> <field name="cache" type="list" default="1" label="COM_MODULES_FIELD_CACHING_LABEL" description="COM_MODULES_FIELD_CACHING_DESC" > <option value="1">JGLOBAL_USE_GLOBAL</option> <option value="0">COM_MODULES_FIELD_VALUE_NOCACHING</option> </field> <field name="cache_time" type="text" default="900" label="COM_MODULES_FIELD_CACHE_TIME_LABEL" description="COM_MODULES_FIELD_CACHE_TIME_DESC" /> <field name="cachemode" type="hidden" default="static"> <option value="static"></option> </field> </fieldset> </fields> </config> </extension>
The text is bold is the additions / changes for Joomla! 4 and later versions.
[12] Why do you have to add this boilerplate yourself instead of com_modules always showing it? Good question! My working theory —since nobody seems to know for sure— is that it was the “easiest” way to let developers decide if they should offer caching options, what is the default caching time, and whether they want to allow alternate view templates. Of course, these could have been attributes under a different key in the XML manifest, avoiding all the annoying boilerplate. I am just annoyed by boilerplate when it can be avoided, I guess.