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

component

A full application with its own administrator and site parts, database tables, menu items, ACL and (optionally) Web Services API routes.

administrator/components/com_x, components/com_x, api/components/com_x, media/com_x

com_x.xml or x.xml

module

A block of output placed in a template position, with a client of site or administrator.

modules/mod_x or administrator/modules/mod_x

mod_x.xml

plugin

An event subscriber that belongs to a group (content, system, task, console, webservices and so on).

plugins/<group>/<element>

<element>.xml

template

A site or administrator template, with a templateDetails.xml.

templates/<name> or administrator/templates/<name>

templateDetails.xml

language

A language pack for the site, the administrator and the API.

language/<tag>, administrator/language/<tag>, api/language/<tag>

install.xml (inside the pack)

library

Shared PHP code under a library name, loaded by other extensions.

libraries/<libraryname>

any name, usually lib_x.xml

file

An arbitrary set of files copied to chosen places in the installation.

Wherever the target attributes point, relative to the site root

any name, usually file_x.xml

package

A container that installs several other extensions in one step.

administrator/manifests/packages/pkg_x.xml

pkg_x.xml (the name is mandatory)

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">
Attribute Valid values Meaning

type

component, file, language, library, module, package, plugin, template

Selects the installer adapter. Required.

method

install (default) or upgrade

With install the installer stops when the extension’s files already exist. With upgrade it installs over the existing version, which is what every extension that publishes updates must use. Core manifests use method="upgrade".

client

site or administrator

Modules (and templates and language packs) only: which application the extension belongs to.

group

any plugin group name

Plugins only: the folder under plugins. An unknown group creates a new folder.

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

<name>

The extension’s name. For components, modules and plugins it is conventionally the technical name (com_tallpine) and is also translatable.

<author>, <authorEmail>, <authorUrl>

The author. Shown in the Extensions Manager.

<creationDate>, <copyright>, <license>

Release information. The licence of an extension distributed to the public must be compatible with the GPL, because it links against Joomla.

<version>

The extension version. Drives the "is this an update" decision and the update server comparison.

<description>

A language key or text, displayed as a tooltip in Manage Extensions and on the installation result page.

<element>

Optional explicit internal name. Defaults to the cleaned <name>.

<packager>, <packagerurl>, <url>

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&amp;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>

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, Site or Api and maps each to the src folder of the matching part. With the prefix Tallpine\Component\Tallpine, the class Tallpine\Component\Tallpine\Administrator\Model\TrailModel lives in administrator/components/com_tallpine/src/Model/TrailModel.php, the site class Tallpine\Component\Tallpine\Site\Model\TrailsModel in components/com_tallpine/src/Model/TrailsModel.php and the API controller Tallpine\Component\Tallpine\Api\Controller\TrailsController in api/components/com_tallpine/src/Controller/TrailsController.php. The installer stores the prefix in the namespace column 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 folder attribute 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 to components/com_tallpine. The <files> inside <administration> is copied to administrator/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 to api/components/com_tallpine and 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 media folder to media/com_tallpine in the web root. This is where joomla.asset.json, stylesheets, scripts and images belong, because media is 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 into administrator/language/<tag>. The Developer Manual’s example lists the file names directly; core manifests list the path relative to folder, for example language/en-GB/com_contact.ini. Language file contents and naming are covered in Language files and internationalisation. The *.sys.ini file 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 (becomes index.php?option=com_tallpine&view=trails), img (the icon, an class: Font Awesome-style name in Joomla 4 and later) and alt. A submenu entry without link and with view builds 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/

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

admin/

administrator/components/com_tallpine/

site/

components/com_tallpine/

api/

api/components/com_tallpine/

media/

media/com_tallpine/

language/en-GB/com_tallpine.ini (site <languages>)

language/en-GB/com_tallpine.ini

language/en-GB/com_tallpine.ini (administration <languages>)

administrator/language/en-GB/com_tallpine.ini

the manifest file

administrator/components/com_tallpine/tallpine.xml

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 the sql folder is listed in the administrator <files>). The driver attribute selects the database type, mysql for MySQL and MariaDB and postgresql for PostgreSQL; the installer also accepts the aliases mysqli, pdomysql and pgsql. Only the file whose driver matches the site’s database is executed. The charset="utf8" attribute is required: a <file> that omits it is silently skipped, and so is its SQL. The files themselves use utf8mb4. <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 #__schemas table.

-- 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")
);

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 updates folder 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>
<?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>

The points specific to these two types:

  • The module= or plugin= attribute on one <folder> (or <filename>) entry names the extension’s element. For a module it must be the full name mod_tallpine_nexthikes; for a plugin it is the short element, here tallpine_conditions, because the group and the plg_ prefix are added by Joomla. The entry is the services folder, which holds provider.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 the params column. Components do not use this element.

  • The group of a plugin decides its folder (plugins/content/tallpine_conditions) and the events it may be subscribed to. Do not create plugins in the behaviour group: 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 default install method, 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>

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>

<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>

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.

flowchart TD Z[pkg_tallpine.zip] --> M[pkg_tallpine.xml] Z --> S[pkg_script.php] Z --> P[packages folder] P --> C[com_tallpine.zip] P --> MOD[mod_tallpine_nexthikes.zip] P --> P1[plg_content_tallpine_conditions.zip] P --> P2[plg_webservices_tallpine.zip] P --> P3[plg_task_tallpine_trailsync.zip] P --> P4[plg_console_tallpine.zip] M -->|installs in order| C M --> MOD M --> P1 M --> P2 M --> P3 M --> P4
<?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>

The rules, from the Manual and the package adapter:

  • The manifest must be named pkg_<packagename>.xml, and packagename is the identity of the package. The installer copies the manifest to administrator/manifests/packages/pkg_tallpine.xml. If the name does not match, uninstalling the package fails.

  • Each <file> carries type, and for the identification of the child at uninstall time also id, client (modules, templates and languages) and group (plugins). The id must equal the element column of the child’s row in #__extensions, otherwise Joomla cannot find the child to uninstall. The Manual’s example uses helloworld as the id of 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), without plg_ and without the group.

  • The plugin group is required, because the uninstall code looks the plugin up by group and element. Two plugins of different groups may share an element, as plg_webservices_tallpine and plg_console_tallpine do above.

  • blockChildUninstall set to true prevents 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 value false or 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 #__extensions has type set to package and 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.

sequenceDiagram participant A as Administrator participant I as Installer participant S as Install script participant D as Database A->>I: upload pkg_tallpine.zip I->>I: unpack and detect type from manifest I->>S: preflight(type) S-->>I: true or false (abort) I->>I: copy files, media and languages I->>D: run install SQL or pending update schemas I->>S: install() or update() I->>I: copy manifest and clean updates records I->>S: postflight(type) I->>A: onExtensionAfterInstall and installer events

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 false from preflight to stop an unsafe install, for instance on an unsupported PHP version. The trait does this for you through $minimumPhp and $minimumJoomla.

  • Do data migrations that depend on the old schema in update(), not in postflight, 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 events onInstallerBeforeInstaller, onExtensionAfterInstall and 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>

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="&amp;dummy=my.zip"/>

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"

Notes on building:

  • zip -r -X records 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=zip with export-ignore entries in .gitattributes is 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, a configuration.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 contain package.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-autoloader into the extension’s own vendor folder, list vendor in <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 type is missing from the root element.

"There is no manifest file" / "Invalid manifest"

The XML is malformed (an unescaped & is the classic case, in a menu link or a download URL), or the file has a byte order mark.

"Extension is already installed" or "files already exist"

method="upgrade" is missing and the files of an earlier install are present. Use method="upgrade".

The component works from the source tree but classes are not found after install

The folder is missing from the <files> list, or <namespace> does not match the PSR-4 path. Clear the cache and, if the namespace map was cached, delete administrator/cache/autoload_psr4.php.

The API routes return 404 after installing

<api><files> is missing, so api/components/com_tallpine was never created, or the webservices plugin is not enabled.

The update never shows in the manager

The update XML does not match: element, type, client, folder, or a targetplatform regular expression that excludes the running version. Check System > Manage > Update Sites.

Uninstalling a package leaves children behind

The id, client or group of a <file> entry does not match the child’s row in #__extensions.

SQL update runs on a fresh install or is skipped on an upgrade

The updates file names do not follow the version order, or the install SQL was not updated to the latest schema.

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_ME in 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) outside media, which is served directly.

  • Remove obsolete files on update through $deleteFiles and $deleteFolders so 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 the api application 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 InstallerScriptInterface and InstallerScriptTrait are the documented approach to install scripts; the plain class with preflight($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 in services/provider.php is where it is wired. See Plugin development and events.

  • Joomla 6.0 no longer defines the JPATH_PLATFORM constant (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 the behaviour group. 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 InstallerScriptInterface with InstallerScriptTrait, described above.

  • The same book’s update XML example limits targetplatform to 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 version attribute on the root element, use a .php entry file per module, call JFactory and JText and guard files with JPATH_PLATFORM. On Joomla 6 the version attribute is ignored, the entry point is a service provider, and the legacy J* 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 use config.xml.

  • Tutorials that pass a plugin’s id in a package as plg_group_name are wrong; the id is the element stored in #__extensions.

For the collected list of outdated claims, see Where older books and tutorials are outdated.