Joomla Extension Types, Manifests and Packaging
|
This content was generated with the assistance of AI and should be verified against the official Joomla documentation before being relied on in production. This sub-section’s Joomla bibliography lists the reference material consulted while preparing these pages. |
Every Joomla extension is installed from an XML file, the manifest. The manifest tells the installer what the extension is, which files to copy and where, which database scripts to run, which language files and media to publish, which PHP namespace to register, which configuration form to show and where to look for updates. This page is the reference for that file for every extension type, for the folder layout a site, administrator and API extension is built from, for the package that bundles several extensions into one download, and for turning a source tree into a zip that installs cleanly.
It was checked on 2026-10-30 against Joomla 6.2.x and PHP 8.3 or later, using the Joomla Developer Manual, the installer classes of the 6.2 source tree (libraries/src/Installer) and the manifests that ship with core. It is written for the extension developer. The running example is the Tallpine Hiking Club package pkg_tallpine, which bundles the component com_tallpine (namespace Tallpine\Component\Tallpine), the module mod_tallpine_nexthikes and four plugins. How an installed extension is managed from the administrator is covered in Extensions: installing, updating and update sites; the scripts and SQL that run during an install are detailed in Installer scripts and SQL updates; update server files are covered in Update servers and the JED.
The eight extension types
Joomla recognises eight extension types. The type is the type attribute of the root <extension> element and decides which installer adapter handles the archive.
| Type | What it is | Installed under | Manifest file name |
|---|---|---|---|
|
A full application with its own administrator and site parts, database tables, menu items, ACL and (optionally) Web Services API routes. |
|
|
|
A block of output placed in a template position, with a |
|
|
|
An event subscriber that belongs to a |
|
|
|
A site or administrator template, with a |
|
|
|
A language pack for the site, the administrator and the API. |
|
|
|
Shared PHP code under a library name, loaded by other extensions. |
|
any name, usually |
|
An arbitrary set of files copied to chosen places in the installation. |
Wherever the |
any name, usually |
|
A container that installs several other extensions in one step. |
|
|
For components, modules and plugins the manifest name must match the extension’s element, because the installer derives the extension’s identity from it. The element comes from an explicit <element> tag, otherwise from the module= or plugin= attribute in <files>, otherwise from the cleaned <name>. Templates always use templateDetails.xml, language packs use install.xml, and library, file and package manifests may have any name, with the exception that a package manifest must be named pkg_<packagename>.xml or its uninstall fails.
The installer finds the manifest by detecting the type of the extracted directory. When the archive contains exactly one entry and that entry is a directory, the installer descends into it, so a zip with a single wrapping folder still installs. A zip whose manifest sits one level deeper next to other files does not.
The root element
<extension type="component" method="upgrade">
Source: Joomla Manual: Manifest files
| Attribute | Valid values | Meaning |
|---|---|---|
|
|
Selects the installer adapter. Required. |
|
|
With |
|
|
Modules (and templates and language packs) only: which application the extension belongs to. |
|
any plugin group name |
Plugins only: the folder under |
Old manifests carry version="3.0" or version="1.6" on the root element. The attribute is a relic from before Joomla 2.5 and is ignored; the manifests that ship with Joomla 6.2 no longer have it, and the example package manifest in the Developer Manual still shows it. The Manual also contains a compatibility table that it marks as out of date, and its body text names the elements <updateservers> and <scriptfile> while the table still lists <updateserver> and <script>. Use the element names in this page, which match the installer source.
Metadata elements
All types share a block of descriptive elements. The Manual recommends at least name, author, version and description.
| Element | Notes |
|---|---|
|
The extension’s name. For components, modules and plugins it is conventionally the technical name ( |
|
The author. Shown in the Extensions Manager. |
|
Release information. The licence of an extension distributed to the public must be compatible with the GPL, because it links against Joomla. |
|
The extension version. Drives the "is this an update" decision and the update server comparison. |
|
A language key or text, displayed as a tooltip in Manage Extensions and on the installation result page. |
|
Optional explicit internal name. Defaults to the cleaned |
|
Package and library metadata, shown in the manager. |
Components: a complete manifest
A component is the most demanding type because it has three application parts (administrator, site, API), a database, a menu and a namespace. This is the manifest of com_tallpine.
<?xml version="1.0" encoding="UTF-8"?>
<extension type="component" method="upgrade">
<name>com_tallpine</name>
<author>Tallpine Hiking Club</author>
<authorEmail>web@tallpine.example</authorEmail>
<authorUrl>https://www.tallpine.example</authorUrl>
<creationDate>2026-10</creationDate>
<copyright>(C) 2026 Tallpine Hiking Club</copyright>
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
<version>1.1.0</version>
<description>COM_TALLPINE_XML_DESCRIPTION</description>
<namespace path="src">Tallpine\Component\Tallpine</namespace>
<scriptfile>script.php</scriptfile>
<changelogurl>https://updates.tallpine.example/com_tallpine/changelog.xml</changelogurl>
<install>
<sql>
<file driver="mysql" charset="utf8">sql/install.mysql.utf8.sql</file>
<file driver="postgresql" charset="utf8">sql/install.postgresql.utf8.sql</file>
</sql>
</install>
<uninstall>
<sql>
<file driver="mysql" charset="utf8">sql/uninstall.mysql.utf8.sql</file>
<file driver="postgresql" charset="utf8">sql/uninstall.postgresql.utf8.sql</file>
</sql>
</uninstall>
<update>
<schemas>
<schemapath type="mysql">sql/updates/mysql</schemapath>
<schemapath type="postgresql">sql/updates/postgresql</schemapath>
</schemas>
</update>
<files folder="site">
<folder>forms</folder>
<folder>src</folder>
<folder>tmpl</folder>
</files>
<languages folder="site">
<language tag="en-GB">language/en-GB/com_tallpine.ini</language>
<language tag="de-DE">language/de-DE/com_tallpine.ini</language>
</languages>
<api>
<files folder="api">
<folder>src</folder>
</files>
</api>
<media destination="com_tallpine" folder="media">
<filename>joomla.asset.json</filename>
<folder>css</folder>
<folder>js</folder>
</media>
<administration>
<menu link="index.php?option=com_tallpine" img="class:hiking">COM_TALLPINE</menu>
<submenu>
<menu view="trails">COM_TALLPINE_SUBMENU_TRAILS</menu>
<menu link="index.php?option=com_categories&extension=com_tallpine">COM_TALLPINE_SUBMENU_CATEGORIES</menu>
</submenu>
<files folder="admin">
<filename>access.xml</filename>
<filename>config.xml</filename>
<folder>forms</folder>
<folder>services</folder>
<folder>sql</folder>
<folder>src</folder>
<folder>tmpl</folder>
</files>
<languages folder="admin">
<language tag="en-GB">language/en-GB/com_tallpine.ini</language>
<language tag="en-GB">language/en-GB/com_tallpine.sys.ini</language>
<language tag="de-DE">language/de-DE/com_tallpine.ini</language>
<language tag="de-DE">language/de-DE/com_tallpine.sys.ini</language>
</languages>
</administration>
</extension>
Source: Joomla Manual: Manifest files
The elements, in the order the installer works with them:
<namespace path="src">-
Registers the PHP namespace prefix of the extension and the source folder that holds its classes. For a component the prefix is the base only; Joomla appends
Administrator,SiteorApiand maps each to thesrcfolder of the matching part. With the prefixTallpine\Component\Tallpine, the classTallpine\Component\Tallpine\Administrator\Model\TrailModellives inadministrator/components/com_tallpine/src/Model/TrailModel.php, the site classTallpine\Component\Tallpine\Site\Model\TrailsModelincomponents/com_tallpine/src/Model/TrailsModel.phpand the API controllerTallpine\Component\Tallpine\Api\Controller\TrailsControllerinapi/components/com_tallpine/src/Controller/TrailsController.php. The installer stores the prefix in thenamespacecolumn of the extension’s row in#__extensions, and Joomla regenerates its namespace map file (administrator/cache/autoload_psr4.php) from those rows. Modules and plugins use the prefix as is. The prefix is described in detail in Namespaces, autoloading and dependency injection. <files>-
The files and folders to copy. The
folderattribute names the source directory inside the zip, and each<filename>and<folder>child is copied to the extension’s directory. The top-level<files>of a component is the site part, copied tocomponents/com_tallpine. The<files>inside<administration>is copied toadministrator/components/com_tallpine. Anything not listed is not installed, which is the most common reason for a class that "works from the source tree but not from the zip". <api>-
Holds a
<files>element for the Web Services API part. The installer copies it toapi/components/com_tallpineand creates that directory if needed; when the component is uninstalled, the directory is removed. Only components can have an<api>element. <media destination="com_tallpine" folder="media">-
Copies the listed folders and files from the zip’s
mediafolder tomedia/com_tallpinein the web root. This is wherejoomla.asset.json, stylesheets, scripts and images belong, becausemediais the only folder meant to be served directly. The same media is shared by the site and administrator parts. See Web Asset Manager for the asset registry file. <languages folder="site">-
Copies language files into the global language folders: the top-level element installs into
language/<tag>, and the element inside<administration>installs intoadministrator/language/<tag>. The Developer Manual’s example lists the file names directly; core manifests list the path relative tofolder, for examplelanguage/en-GB/com_contact.ini. Language file contents and naming are covered in Language files and internationalisation. The*.sys.inifile holds the strings the installer and the Extensions Manager need (the name, the description and the menu title) and is loaded outside the normal request. <administration>-
Components only. It contains the administrator files, language files,
<menu>,<submenu>and optional<dashboards>. <menu>and<submenu>-
The administrator menu entry. The text is a language key. Attributes:
link(escape&as&),view(becomesindex.php?option=com_tallpine&view=trails),img(the icon, anclass:Font Awesome-style name in Joomla 4 and later) andalt. A submenu entry withoutlinkand withviewbuilds the URL from the view name. <dashboards>-
Defines administrator dashboards (
<dashboard title="..." icon="...">name</dashboard>) that a menu item can open with<params><dashboard>name</dashboard></params>. The name must match the dashboard element. <scriptfile>-
The install script, covered below.
<changelogurl>-
Where the Extensions Manager finds the changelog for this extension, covered below.
Two elements that new developers expect do not apply to components. <config> is for modules, plugins and templates only; a component’s configuration form is the file config.xml in its administrator folder, edited in the Options toolbar button. The ACL rules of a component are the file access.xml, discussed in ACL in extensions.
The folder layout that goes with it
The manifest above expects a source tree like this.
com_tallpine/
├── tallpine.xml manifest
├── script.php install script
├── admin/ copied to administrator/components/com_tallpine
│ ├── access.xml
│ ├── config.xml
│ ├── forms/
│ ├── services/provider.php
│ ├── sql/
│ │ ├── install.mysql.utf8.sql
│ │ ├── install.postgresql.utf8.sql
│ │ ├── uninstall.mysql.utf8.sql
│ │ ├── uninstall.postgresql.utf8.sql
│ │ └── updates/
│ │ ├── mysql/1.1.0.sql
│ │ └── postgresql/1.1.0.sql
│ ├── src/ Tallpine\Component\Tallpine\Administrator\
│ └── tmpl/
├── site/ copied to components/com_tallpine
│ ├── forms/
│ ├── src/ Tallpine\Component\Tallpine\Site\
│ └── tmpl/
├── api/ copied to api/components/com_tallpine
│ └── src/ Tallpine\Component\Tallpine\Api\
├── media/ copied to media/com_tallpine
│ ├── joomla.asset.json
│ ├── css/
│ └── js/
└── language/ copied to the global language folders
├── en-GB/
└── de-DE/
Source: Joomla Manual: Manifest files
The names admin, site and api are conventions of the folder attribute, not requirements; core uses admin and site for its components. The installed layout in a running site is different from the source tree, and that difference is the whole point of the manifest:
| Source folder in the zip | Installed in the Joomla tree |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
the manifest file |
|
SQL: install, uninstall and update scripts
Database scripts are declared in three places of the component manifest.
<install><sql>and<uninstall><sql>-
Each
<file>names an SQL file relative to the installed extension root (administrator/components/com_tallpine, which is why thesqlfolder is listed in the administrator<files>). Thedriverattribute selects the database type,mysqlfor MySQL and MariaDB andpostgresqlfor PostgreSQL; the installer also accepts the aliasesmysqli,pdomysqlandpgsql. Only the file whose driver matches the site’s database is executed. Thecharset="utf8"attribute is required: a<file>that omits it is silently skipped, and so is its SQL. The files themselves useutf8mb4.<uninstall>runs when the extension is removed through the Extensions Manager. <update><schemas><schemapath type="mysql">-
A folder of files named after versions (
1.1.0.sql,1.2.0.sql). The installer lists the files of the folder matching the server type and runs those with a version higher than the schema version recorded for the extension in the#__schemastable.
-- sql/install.mysql.utf8.sql
CREATE TABLE IF NOT EXISTS `#__tallpine_trails` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`asset_id` int unsigned NOT NULL DEFAULT 0,
`catid` int unsigned NOT NULL DEFAULT 0,
`title` varchar(255) NOT NULL,
`alias` varchar(400) NOT NULL DEFAULT '',
`distance_km` decimal(6,2) NOT NULL DEFAULT 0.00,
`elevation_gain` int unsigned NOT NULL DEFAULT 0,
`state` tinyint NOT NULL DEFAULT 0,
`access` int unsigned NOT NULL DEFAULT 0,
`created` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_catid` (`catid`),
KEY `idx_state` (`state`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 DEFAULT COLLATE = utf8mb4_unicode_ci;
-- sql/uninstall.mysql.utf8.sql
DROP TABLE IF EXISTS `#__tallpine_trails`;
-- sql/updates/mysql/1.1.0.sql
ALTER TABLE `#__tallpine_trails` ADD COLUMN `difficulty` varchar(20) NOT NULL DEFAULT 'moderate';
-- sql/install.postgresql.utf8.sql
CREATE TABLE IF NOT EXISTS "#__tallpine_trails" (
"id" serial NOT NULL,
"asset_id" bigint NOT NULL DEFAULT 0,
"catid" bigint NOT NULL DEFAULT 0,
"title" varchar(255) NOT NULL,
"alias" varchar(400) NOT NULL DEFAULT '',
"distance_km" numeric(6,2) NOT NULL DEFAULT 0.00,
"elevation_gain" bigint NOT NULL DEFAULT 0,
"state" smallint NOT NULL DEFAULT 0,
"access" bigint NOT NULL DEFAULT 0,
"created" timestamp without time zone NOT NULL,
PRIMARY KEY ("id")
);
Source: Joomla Manual: Manifest files
The prefix #__ is replaced with the site’s table prefix when the script runs. The Joomla query builder does not make hand-written installation SQL portable: a component that supports both databases ships both sets of files, as above, and one that supports only MySQL and MariaDB omits the postgresql entries. Several rules explain most failures:
-
On a first installation the installer records the highest file name in the
updatesfolder as the schema version and runs none of the update files. A fresh install is expected to create the final schema from the install file, so the install file must always represent the newest schema. -
On an update the installer runs, in order, the update files whose names are numerically higher than the recorded version. The
<version>of the manifest and the schema version are tracked separately; keeping the names of the update files in step with the manifest versions avoids confusion, and the Manual recommends it. -
An update file is plain SQL, one statement per terminating semicolon. A failing statement stops the update and leaves the extension in a half-updated state, which the System > Maintenance > Database tab reports as a schema mismatch.
-
Update files are never edited after release. Fixing a mistake means a new, higher file.
Modules and plugins
Modules and plugins have no administration part, no <api> and no SQL by convention (though SQL elements are accepted). Their manifests are short, and the entry point is a service provider.
<?xml version="1.0" encoding="UTF-8"?>
<extension type="module" client="site" method="upgrade">
<name>mod_tallpine_nexthikes</name>
<author>Tallpine Hiking Club</author>
<creationDate>2026-10</creationDate>
<copyright>(C) 2026 Tallpine Hiking Club</copyright>
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
<version>1.1.0</version>
<description>MOD_TALLPINE_NEXTHIKES_XML_DESCRIPTION</description>
<namespace path="src">Tallpine\Module\NextHikes</namespace>
<files>
<folder module="mod_tallpine_nexthikes">services</folder>
<folder>src</folder>
<folder>tmpl</folder>
</files>
<languages>
<language tag="en-GB">language/en-GB/mod_tallpine_nexthikes.ini</language>
<language tag="en-GB">language/en-GB/mod_tallpine_nexthikes.sys.ini</language>
</languages>
<config>
<fields name="params">
<fieldset name="basic">
<field
name="count"
type="number"
label="MOD_TALLPINE_NEXTHIKES_FIELD_COUNT_LABEL"
default="5"
min="1"
max="20"
filter="integer"
/>
</fieldset>
</fields>
</config>
</extension>
Source: Joomla Manual: Manifest files
<?xml version="1.0" encoding="UTF-8"?>
<extension type="plugin" group="content" method="upgrade">
<name>plg_content_tallpine_conditions</name>
<author>Tallpine Hiking Club</author>
<creationDate>2026-10</creationDate>
<copyright>(C) 2026 Tallpine Hiking Club</copyright>
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
<version>1.1.0</version>
<description>PLG_CONTENT_TALLPINE_CONDITIONS_XML_DESCRIPTION</description>
<namespace path="src">Tallpine\Plugin\Content\TallpineConditions</namespace>
<files>
<folder plugin="tallpine_conditions">services</folder>
<folder>src</folder>
</files>
<languages>
<language tag="en-GB">language/en-GB/plg_content_tallpine_conditions.ini</language>
<language tag="en-GB">language/en-GB/plg_content_tallpine_conditions.sys.ini</language>
</languages>
<config>
<fields name="params">
<fieldset name="basic">
<field
name="show_banner"
type="radio"
layout="joomla.form.field.radio.switcher"
label="PLG_CONTENT_TALLPINE_CONDITIONS_FIELD_BANNER_LABEL"
default="1"
filter="integer"
>
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
</fieldset>
</fields>
</config>
</extension>
Source: Joomla Manual: Manifest files
The points specific to these two types:
-
The
module=orplugin=attribute on one<folder>(or<filename>) entry names the extension’s element. For a module it must be the full namemod_tallpine_nexthikes; for a plugin it is the short element, heretallpine_conditions, because the group and theplg_prefix are added by Joomla. The entry is theservicesfolder, which holdsprovider.php, the dependency injection service provider that creates the module dispatcher factory or the plugin instance. A legacy<filename module="mod_x">mod_x.php</filename>entry still works for old-style entry points but new modules and plugins should use the service provider. See Module development and Plugin development and events. -
<config>holds the form shown in the module or plugin editor. The outer<fields name="params">means the values are stored as JSON in theparamscolumn. Components do not use this element. -
The
groupof a plugin decides its folder (plugins/content/tallpine_conditions) and the events it may be subscribed to. Do not create plugins in thebehaviourgroup: the Joomla 6.0 backward-compatibility plugins live there and are treated specially by the updater. -
A plugin or module is typically
method="upgrade"so that an update replaces its files. With the defaultinstallmethod, a second installation of the same extension is refused when its files already exist. -
The element name of a plugin is unique within its group. Choose a prefix (
tallpine_) that cannot collide with another vendor.
Plugins of the other Tallpine groups follow exactly the same pattern with a different group and namespace: plg_webservices_tallpine (group webservices), plg_task_tallpine_trailsync (group task, see Scheduled task plugins) and plg_console_tallpine (group console, see Console command plugins).
Templates
A template is installed from a templateDetails.xml with type="template" and a client. Templates keep their <files> in the template folder, their assets under <media> and their form in <config>, and add <positions>. The full file is explained in Building a template from scratch; a child template additionally declares <inheritable> and <parent> as described in Child templates. Only the general parts that overlap with other types are listed here.
<?xml version="1.0" encoding="UTF-8"?>
<extension type="template" client="site" method="upgrade">
<name>cassiopeia_tallpine</name>
<version>1.1.0</version>
<creationDate>2026-10</creationDate>
<author>Tallpine Hiking Club</author>
<description>TPL_CASSIOPEIA_TALLPINE_XML_DESCRIPTION</description>
<parent>cassiopeia</parent>
<files>
<filename>templateDetails.xml</filename>
<filename>index.php</filename>
<folder>html</folder>
<folder>language</folder>
</files>
<media folder="media" destination="templates/site/cassiopeia_tallpine">
<folder>css</folder>
<filename>joomla.asset.json</filename>
</media>
<positions>
<position>trail-conditions</position>
</positions>
</extension>
Source: Joomla Manual: Manifest files
Templates are installed into templates/<name> for the site and administrator/templates/<name> for the administrator. A template that is referenced by template styles cannot be uninstalled until the styles are removed or reassigned.
Language packs
A language pack has type="language" and, unlike a component, targets up to three applications. Translation packs from the official translation teams are language packages: a package that contains one language extension per application. The en-GB pack that ships with Joomla 6.2 is written exactly that way, with a client of site, administrator and api.
<?xml version="1.0" encoding="UTF-8"?>
<extension type="package" method="upgrade">
<name>English (en-GB) Language Pack</name>
<packagename>en-GB</packagename>
<version>6.2.1.1</version>
<author>Joomla! Project</author>
<packager>Joomla! Project</packager>
<blockChildUninstall>true</blockChildUninstall>
<files>
<folder type="language" client="site" id="en-GB">language/en-GB</folder>
<folder type="language" client="administrator" id="en-GB">administrator/language/en-GB</folder>
<folder type="language" client="api" id="en-GB">api/language/en-GB</folder>
</files>
<updateservers>
<server type="collection" priority="1" name="Accredited Joomla! Translations">
https://update.joomla.org/language/translationlist_6.xml
</server>
</updateservers>
</extension>
Inside such a language folder the file install.xml lists the .ini files with <languages folder="language"><language tag="en-GB">com_example.ini</language>. A Tallpine German pack would follow the same pattern with the tag de-DE. Third-party extensions do not usually ship a pack; they ship their .ini files through the <languages> element of their own manifest.
Libraries
A library is shared PHP code. It has no namespace registration of its own in the manifest; the code is loaded either by its own autoloader, by Composer or by the extensions that use it.
<?xml version="1.0" encoding="UTF-8"?>
<extension type="library" method="upgrade">
<name>lib_tallpine_gpx</name>
<libraryname>tallpine/gpx</libraryname>
<version>1.0.0</version>
<creationDate>2026-10</creationDate>
<author>Tallpine Hiking Club</author>
<description>LIB_TALLPINE_GPX_XML_DESCRIPTION</description>
<files>
<folder>src</folder>
<filename>gpx.php</filename>
</files>
</extension>
Source: Joomla Manual: Manifest files
<libraryname> is the target directory below libraries/. A slash creates a vendor group: the example above installs into libraries/tallpine/gpx. The core library manifest lib_joomla uses a plain name (joomla) and copies its own src, loader.php and platform.php. Libraries have no client and no group. Compared with bundling a vendor folder inside each component, a library lets several extensions share a dependency, at the price of version conflicts between extensions that need different releases of it; for a single extension, a vendor folder inside its own src tree (see Composer and packages) is usually safer.
File extensions
A type="file" extension copies arbitrary files to places relative to the site root. Core itself uses it for the CMS files: the manifest files_joomla declares a <fileset> of folders and files to overwrite on every update, plus a <scriptfile> and update schemas.
<?xml version="1.0" encoding="UTF-8"?>
<extension type="file" method="upgrade">
<name>file_tallpine_samples</name>
<version>1.0.0</version>
<creationDate>2026-10</creationDate>
<author>Tallpine Hiking Club</author>
<description>FILE_TALLPINE_SAMPLES_XML_DESCRIPTION</description>
<fileset>
<files target="images/tallpine">
<folder>trail-photos</folder>
<filename>default-map.png</filename>
</files>
</fileset>
</extension>
Source: joomla-cms: files_joomla.xml
The target attribute is relative to the Joomla root; omitting it targets the root itself, which can overwrite core files, so it must be used with care. A file extension is the right tool for sample content or a one-off asset drop. It is the wrong tool for code, because files outside an extension’s own folder are not covered by uninstall hygiene beyond what the fileset lists.
Packages
A package is the unit in which the Tallpine club ships its software. It is a manifest plus a folder of ready-made zips, and the installer unpacks each zip and installs it with the adapter of its own type, in the order listed.
<?xml version="1.0" encoding="UTF-8"?>
<extension type="package" method="upgrade">
<name>Tallpine Hiking Club Package</name>
<packagename>tallpine</packagename>
<version>1.1.0</version>
<creationDate>2026-10</creationDate>
<author>Tallpine Hiking Club</author>
<authorEmail>web@tallpine.example</authorEmail>
<url>https://www.tallpine.example</url>
<packager>Tallpine Hiking Club</packager>
<packagerurl>https://www.tallpine.example</packagerurl>
<description>PKG_TALLPINE_XML_DESCRIPTION</description>
<scriptfile>pkg_script.php</scriptfile>
<blockChildUninstall>true</blockChildUninstall>
<changelogurl>https://updates.tallpine.example/pkg_tallpine/changelog.xml</changelogurl>
<files folder="packages">
<file type="component" id="com_tallpine">com_tallpine.zip</file>
<file type="module" client="site" id="mod_tallpine_nexthikes">mod_tallpine_nexthikes.zip</file>
<file type="plugin" group="content" id="tallpine_conditions">plg_content_tallpine_conditions.zip</file>
<file type="plugin" group="webservices" id="tallpine">plg_webservices_tallpine.zip</file>
<file type="plugin" group="task" id="tallpine_trailsync">plg_task_tallpine_trailsync.zip</file>
<file type="plugin" group="console" id="tallpine">plg_console_tallpine.zip</file>
</files>
<updateservers>
<server type="extension" priority="1" name="Tallpine Hiking Club Package">https://updates.tallpine.example/pkg_tallpine/update.xml</server>
</updateservers>
</extension>
Source: Joomla Manual: Packages
The rules, from the Manual and the package adapter:
-
The manifest must be named
pkg_<packagename>.xml, andpackagenameis the identity of the package. The installer copies the manifest toadministrator/manifests/packages/pkg_tallpine.xml. If the name does not match, uninstalling the package fails. -
Each
<file>carriestype, and for the identification of the child at uninstall time alsoid,client(modules, templates and languages) andgroup(plugins). Theidmust equal theelementcolumn of the child’s row in#__extensions, otherwise Joomla cannot find the child to uninstall. The Manual’s example useshelloworldas theidof a module; the stored element of a module includes its prefix (mod_tallpine_nexthikes), and the element of a plugin is the short name (tallpine_conditions), withoutplg_and without the group. -
The plugin
groupis required, because the uninstall code looks the plugin up by group and element. Two plugins of different groups may share an element, asplg_webservices_tallpineandplg_console_tallpinedo above. -
blockChildUninstallset totrueprevents an administrator from uninstalling a child on its own; it can then only be removed with the whole package. The Joomla en-GB language pack uses it. With the valuefalseor omitted, children can be removed one by one, and removing the component while the module remains leaves a module that calls into a missing component. -
A package has no folder of its own, no client and no group; its row in
#__extensionshastypeset topackageand an empty folder. -
Children may also be unzipped folders instead of zips: put the folder name in
<file>and install the package with Install from Folder. -
A package-level
<updateservers>entry updates the package as a unit. Children can also have their own update servers, but updating a child on its own will fight with the package update; choose one scheme per extension. Update servers are described in Update servers and the JED. -
A package-level
<scriptfile>runs for the package itself, before and after its children are installed. The children run their own scripts. -
Installation of a package is not transactional. If the fourth child fails, the first three stay installed. Order the children so that dependencies come first (library before component, component before the modules and plugins that use it) and keep install scripts idempotent.
The uninstall of a package removes the children in the order the installer finds them, then the package row and manifest. Because of this, a package is also the cleanest way to ship a plugin that is only useful together with a component, such as the webservices plugin that registers the routes of com_tallpine.
The install script
The <scriptfile> element names a PHP file, relative to the zip root, that Joomla loads while installing. The file either defines a class, or, in the current style, returns a service provider that registers an object implementing Joomla\CMS\Installer\InstallerScriptInterface in the container. The interface has five methods.
namespace Joomla\CMS\Installer;
interface InstallerScriptInterface
{
public function install(InstallerAdapter $adapter): bool;
public function update(InstallerAdapter $adapter): bool;
public function uninstall(InstallerAdapter $adapter): bool;
public function preflight(string $type, InstallerAdapter $adapter): bool;
public function postflight(string $type, InstallerAdapter $adapter): bool;
}
The order of events during an install or update is the following.
preflight runs before any file is copied and can abort the installation by returning false. install, update or uninstall runs part way through, after the database work. postflight runs near the end, after the manifest is copied and the stale #__updates record is removed, and before the onExtensionAfterInstall and onInstallerAfterInstaller events fire. The $type passed to preflight and postflight tells which operation is running.
InstallerScriptTrait supplies the common behaviour. Its protected properties are $minimumPhp, $minimumJoomla (both skipped when empty), $allowDowngrades (default false), $deleteFiles and $deleteFolders (paths relative to the site root). Its preflight checks the PHP and Joomla minimums and refuses a downgrade, and its postflight deletes the listed files and folders, so an update can remove files that an earlier release shipped. The trait also offers getOldManifest() to read the previously installed manifest, and addDashboardMenuModule() to create an administrator mod_submenu module in a dashboard position.
<?php
\defined('_JEXEC') or die;
use Joomla\CMS\Application\AdministratorApplication;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Installer\InstallerScriptInterface;
use Joomla\CMS\Installer\InstallerScriptTrait;
use Joomla\CMS\Language\Text;
use Joomla\Database\DatabaseInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
return new class () implements ServiceProviderInterface {
public function register(Container $container): void
{
$container->set(
InstallerScriptInterface::class,
new class (
$container->get(AdministratorApplication::class),
$container->get(DatabaseInterface::class)
) implements InstallerScriptInterface {
use InstallerScriptTrait {
InstallerScriptTrait::preflight as traitPreflight;
InstallerScriptTrait::postflight as traitPostflight;
}
protected string $minimumPhp = '8.3.0';
protected string $minimumJoomla = '6.2.0';
protected array $deleteFiles = [
'/components/com_tallpine/tmpl/trails/legacy.php',
];
protected array $deleteFolders = [];
public function __construct(
private AdministratorApplication $app,
private DatabaseInterface $db
) {
}
public function install(InstallerAdapter $adapter): bool
{
$this->app->enqueueMessage(Text::_('COM_TALLPINE_INSTALLED'));
return true;
}
public function update(InstallerAdapter $adapter): bool
{
$this->app->enqueueMessage(Text::_('COM_TALLPINE_UPDATED'));
return true;
}
public function uninstall(InstallerAdapter $adapter): bool
{
return true;
}
public function preflight(string $type, InstallerAdapter $adapter): bool
{
return $this->traitPreflight($type, $adapter);
}
public function postflight(string $type, InstallerAdapter $adapter): bool
{
return $this->traitPostflight($type, $adapter);
}
}
);
}
};
Practical guidance for install scripts:
-
Return
falsefrompreflightto stop an unsafe install, for instance on an unsupported PHP version. The trait does this for you through$minimumPhpand$minimumJoomla. -
Do data migrations that depend on the old schema in
update(), not inpostflight, and make them safe to run twice, because administrators re-run failed updates. -
The script runs with the administrator’s session and the full rights of the web server. Never execute strings taken from the uploaded archive, never write outside the extension’s own folders and database tables, and do not enqueue unescaped HTML coming from the database. See Security in extensions.
-
Installer plugins (group
installer) and the eventsonInstallerBeforeInstaller,onExtensionAfterInstalland related can also hook into the process, and are the right place for behaviour that belongs to the site rather than to one extension.
Older books such as Developing Extensions for Joomla! 5 (2023) show an installer script as a plain class that extends nothing and registers no interface. That style still works for a manifest <scriptfile> that defines a class, but the documented approach is InstallerScriptInterface with InstallerScriptTrait, optionally returned from a service provider as above. A script that guards itself with JPATH_PLATFORM also needs rewriting for Joomla 6, which no longer defines that constant unless the "Behaviour - Backward Compatibility 6" plugin is enabled; use defined('_JEXEC') or die; as in the example.
Changelogs
<changelogurl> points to an XML file with the release notes for each version. The Extensions Manager shows a changelog button next to the version, and the Update screen shows it next to an available update. Place the element in the extension manifest for the Manage screen, and in the update server’s <update> entry for the Update screen. The URL must have no spaces or line breaks before or after it, or it is not read. When the requested version is not found in the file, the button is not shown.
<?xml version="1.0" encoding="UTF-8"?>
<changelogs>
<changelog>
<element>com_tallpine</element>
<type>component</type>
<version>1.1.0</version>
<security>
<item>Escaped the trail title in the conditions banner.</item>
</security>
<fix>
<item>The elevation filter ignored the upper bound.</item>
</fix>
<addition>
<item>New difficulty field on trails.</item>
</addition>
<change>
<item>Requires Joomla 6.2 or later.</item>
</change>
<note>
<item><![CDATA[Run the update from <strong>System > Update > Extensions</strong>.]]></item>
</note>
</changelog>
</changelogs>
Source: Joomla Manual: Changelogs
Each <changelog> needs element, type and version. The change types are security, fix, language, addition, change, remove and note; each contains one or more <item> nodes. Text may be plain or HTML, and HTML must be wrapped in CDATA.
Update servers and download keys in the manifest
Extensions declare where Joomla looks for new versions with <updateservers>. The installer registers each server as an update site in #__update_sites, linked to the extension.
<updateservers>
<server type="extension" priority="1" name="Tallpine Hiking Club">https://updates.tallpine.example/com_tallpine/update.xml</server>
</updateservers>
<dlid prefix="dlid=" suffix="&dummy=my.zip"/>
Source: Joomla Manual: Update servers
type="extension" points at the update XML of a single extension and type="collection" at a list that points to several extensions, such as the translation list used by language packs. priority orders checks when there are several servers. name is optional and must not be a language key, because Joomla may not translate it. The optional <dlid> element wraps a download key stored by the administrator in the update site, so that a commercial extension can append it to the download URL. The update XML itself, with targetplatform, php_minimum, supported_databases, stability tags and checksums, is the subject of Update servers and the JED.
A common mistake in older material concerns targetplatform. Developing Extensions for Joomla! 5 shows an update XML whose Joomla pattern stops at version 5.0 and whose database minimums are those of Joomla 4. Because the pattern is a regular expression matched against the running Joomla version, such a file hides every update from Joomla 5.1 and from Joomla 6. Use a pattern that covers the supported range, for example (5\.[4-9|6\.[0-9]+)], and state the PHP minimum with <php_minimum>8.3</php_minimum>.
Building the zips
Joomla installs from ordinary zip archives (and tar files). There is no packager built into core, so the build is a small script run by the developer or by CI. The three constraints are: the manifest must be at the root of each archive (or in a single wrapping folder), each child zip must be a complete extension of its own, and the package zip must contain the package manifest, the optional script and the packages folder with the child zips.
#!/usr/bin/env bash
set -euo pipefail
VERSION="1.1.0"
OUT="dist"
rm -rf "$OUT" && mkdir -p "$OUT/packages"
# Each child zip: the manifest and the folders it references, at the zip root.
(cd com_tallpine && zip -r -X "../$OUT/packages/com_tallpine.zip" . -x '*.DS_Store' -x 'tests/*')
(cd mod_tallpine_nexthikes && zip -r -X "../$OUT/packages/mod_tallpine_nexthikes.zip" . -x '*.DS_Store')
for plugin in plg_content_tallpine_conditions plg_webservices_tallpine plg_task_tallpine_trailsync plg_console_tallpine; do
(cd "$plugin" && zip -r -X "../$OUT/packages/$plugin.zip" . -x '*.DS_Store')
done
# The package zip: manifest, script and the packages folder.
cp pkg_tallpine/pkg_tallpine.xml pkg_tallpine/pkg_script.php "$OUT/"
(cd "$OUT" && zip -r -X "pkg_tallpine-$VERSION.zip" pkg_tallpine.xml pkg_script.php packages)
# Checksum for the update server entry.
shasum -a 384 "$OUT/pkg_tallpine-$VERSION.zip"
Source: Joomla Manual: Packages
Notes on building:
-
zip -r -Xrecords no extra file attributes and keeps the archive reproducible. Run it inside the extension folder so that the manifest ends up at the root of the zip; zipping the parent folder produces a single wrapping folder, which Joomla tolerates, but a parent folder that also contains other files does not. -
git archive --format=zipwithexport-ignoreentries in.gitattributesis an alternative that cannot accidentally include untracked or ignored files such as.env,node_modules, test fixtures or IDE folders. Anything in the zip is public: never include a.env, aconfiguration.php, credentials or API tokens. -
Build the frontend assets (JavaScript and CSS) before zipping, and ship only the compiled files under
media. Production zips must not containpackage.json, tests or source maps unless they are wanted. Automating all of this with GitHub Actions, PHPCS, PHPStan and PHPUnit is covered in Build and CI for extensions. -
Keep versions consistent: the
<version>in each manifest, the highest SQL update file name, the package<version>, the update XML and the changelog should all describe the same release. -
Libraries from Composer: run
composer install --no-dev --optimize-autoloaderinto the extension’s ownvendorfolder, listvendorin<files>and prefix or isolate the namespaces if another extension may ship a different release of the same library. -
Sign off the result by installing the zip on a clean Joomla 6.2 site, with debug mode enabled, in both the install and the update path (install the previous release, then the new one), and uninstalling it to prove that no table, file or database row remains.
Common errors and pitfalls
| Symptom | Usual cause |
|---|---|
"Unable to find install package" or "Unknown package type" |
The manifest is not at the zip root, the zip contains several top-level entries, or |
"There is no manifest file" / "Invalid manifest" |
The XML is malformed (an unescaped |
"Extension is already installed" or "files already exist" |
|
The component works from the source tree but classes are not found after install |
The folder is missing from the |
The API routes return 404 after installing |
|
The update never shows in the manager |
The update XML does not match: |
Uninstalling a package leaves children behind |
The |
SQL update runs on a fresh install or is skipped on an upgrade |
The |
Database errors only on PostgreSQL |
Only the MySQL install and update files were written; each driver needs its own file. |
Security checklist for packaging
-
Treat the zip as an executable. Everything in it runs with the web server’s rights. Sign and publish release hashes with
<sha384>in the update XML so that clients verify the download. -
Serve update XML, changelogs and zips over HTTPS only, and from a host you control.
-
Do not ship secrets, test credentials or development configuration files. Provide placeholders such as
CHANGE_MEin documentation, and read real values from the administrator-managed component options or from environment variables. -
Guard every PHP file that can be requested directly with
defined('_JEXEC') or die;. Keep non-public data (cached files, uploads) outsidemedia, which is served directly. -
Remove obsolete files on update through
$deleteFilesand$deleteFoldersso that a vulnerable file removed in a release is not left behind on upgraded sites. -
Declare the lowest supported Joomla and PHP versions in the install script and in the update XML so that the extension never loads on a platform where its security assumptions fail.
-
Extensions published through the Joomla Extensions Directory are scanned and may be listed on the Vulnerable Extensions List if a flaw is found; see Update servers and the JED.
Version notes
-
Namespaced extensions with
services/provider.php,<namespace path="src">and theapiapplication arrived with Joomla 4.0 and are the only supported structure for new work. Legacy non-namespaced entry points still run on the Joomla 6 line. -
The restructured
InstallerScriptInterfaceandInstallerScriptTraitare the documented approach to install scripts; the plain class withpreflight($type, $parent)of Joomla 3 remains accepted. -
Since Joomla 5.0, plugins use concrete event classes with
SubscriberInterface; the manifest is unchanged by that, but the plugin’s entry inservices/provider.phpis where it is wired. See Plugin development and events. -
Joomla 6.0 no longer defines the
JPATH_PLATFORMconstant (it exists only with the Backward Compatibility 6 plugin) and moved the legacy class aliases into that plugin, which is off by default on new installs, so install scripts and entry files written for Joomla 3 or 4 must be updated. Never put your own plugins in thebehaviourgroup. The removals are collected in Deprecations and preparing for Joomla 7. -
From Joomla 6.2, extension updates can carry a security-severity flag in the Update screen; see Extensions: installing, updating and update sites.
-
The Developer Manual pages cited here are headed with version 6.1; where a detail matters, the installer classes of the 6.2 source tree were used to confirm it.
Corrections to older material
Older books and tutorials describe the same manifest with differences that matter:
-
Developing Extensions for Joomla! 5 (2023) shows an installer script that extends nothing. Current practice is
InstallerScriptInterfacewithInstallerScriptTrait, described above. -
The same book’s update XML example limits
targetplatformto Joomla 5.0 and lists Joomla 4-era database minimums, which hides updates from Joomla 5.1 and later. Use a pattern that covers the supported range, and the database and PHP minimums of the release. -
Tutorials for Joomla 3 declare a
versionattribute on the root element, use a.phpentry file per module, callJFactoryandJTextand guard files withJPATH_PLATFORM. On Joomla 6 theversionattribute is ignored, the entry point is a service provider, and the legacyJ*class aliases are available only through the Backward Compatibility 6 plugin. -
Material that describes the manifest
<config>for a component confuses it with the module and plugin form; components useconfig.xml. -
Tutorials that pass a plugin’s
idin a package asplg_group_nameare wrong; theidis the element stored in#__extensions.
For the collected list of outdated claims, see Where older books and tutorials are outdated.