Joomla Update Servers and the Joomla Extensions Directory

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.

An extension that is installed on hundreds of sites is only as safe as its update path. Joomla does not push updates: each site periodically downloads small XML files from the update servers that the extension’s manifest names, compares them with what is installed and offers the newest suitable release. The XML is therefore the contract between the developer and every site, and the Joomla Extensions Directory (JED) is the public catalogue that points users to extensions that follow the rules of that contract. This page is the developer’s reference for both.

It was checked on 2026-10-30 against Joomla 6.2.x and PHP 8.3 or later, using the Joomla Developer Manual, the updater classes of the 6.2 source tree (libraries/src/Updater, libraries/src/Installer/InstallerHelper.php and administrator/components/com_installer) and the public JED site. The running example is the Tallpine Hiking Club package pkg_tallpine with its component com_tallpine (namespace Tallpine\Component\Tallpine). The administrator’s side (the Update Sites screen, Rebuild, the cache time and the minimum stability) is covered in Extensions: installing, updating and update sites; the manifest elements are in Extension types, manifests and packaging; building and publishing from a pipeline is in Build and CI for extensions. This page goes into the update XML itself and what the updater does with it.

The update flow end to end

flowchart TD A[Manifest: updateservers server] -->|installer registers| B[(update_sites table)] B --> C[Updater fetches each enabled site] C --> D{type} D -->|collection| E[extensionset lists detailsurl per extension] E --> C D -->|extension| F[Parse update entries] F --> G{targetplatform matches Joomla version?} G -->|no| X[ignored] G -->|yes| H{php_minimum, supported_databases, stability, channel ok?} H -->|no| W[warning shown, not installable] H -->|yes| I[highest version wins] I --> J[(updates table)] J --> K[Administrator selects Update] K --> L[Download from downloadurl plus download key] L --> M{checksum} M -->|mismatch| N[update stops] M -->|match or absent| O[Installer update path]

When the administrator opens System → Update → Extensions (or the scheduled check runs), the updater loads every enabled row of #__update_sites. A site of type collection yields more locations; a site of type extension yields update entries. The entries that pass the filters described below are reduced to the single highest version per extension and stored in #__updates. Selecting Update re-reads the XML, downloads the zip, verifies the checksum and hands the file to the installer, which runs the adapter’s update path, the SQL update files and the install script (see Installer scripts and SQL updates).

Extension update sites are plain XML over HTTPS. The The Update Framework (TUF) metadata that protects core downloads since Joomla 5.1 is a separate mechanism for the core update site: in the 6.2 source, UpdateModel refuses an extension update site of type tuf with the message JLIB_INSTALLER_TUF_NOT_AVAILABLE, so third-party extensions cannot rely on it. Their integrity guarantees are HTTPS and the checksum elements, discussed under signing below.

Declaring the update server

The manifest of the extension (or, better, of the package) declares where the XML lives:

<!-- pkg_tallpine.xml (excerpt) -->
<extension type="package" method="upgrade">
    <name>PKG_TALLPINE</name>
    <packagename>tallpine</packagename>
    <version>1.4.0</version>
    <changelogurl>https://updates.tallpine.example/changelog.xml</changelogurl>
    <updateservers>
        <server type="extension" priority="1" name="Tallpine Hiking Club">https://updates.tallpine.example/pkg_tallpine.xml</server>
    </updateservers>
</extension>

Declare the server once, on the package, rather than on each child extension: a package whose children also declare servers produces several update sites and several prompts for one product. The URL must have no spaces or line breaks around it. The name is shown in the Update Sites list as written, so do not use a language key. type="collection" points to an extensionset file instead (used by language packs and by vendors with many products):

<?xml version="1.0" encoding="utf-8"?>
<extensionset name="Tallpine Hiking Club extensions" description="All Tallpine extensions">
    <extension name="Tallpine package" element="pkg_tallpine" type="package" version="1.4.1"
               detailsurl="https://updates.tallpine.example/pkg_tallpine.xml"/>
    <extension name="Next hikes module" element="mod_tallpine_nexthikes" type="module" client="site" version="1.2.0"
               detailsurl="https://updates.tallpine.example/mod_tallpine_nexthikes.xml"/>
</extensionset>

Each extension entry needs name, element, type, version and detailsurl, which points at that extension’s own update XML.

The update XML

A complete update file for the Tallpine package, with every field a careful developer sets:

<?xml version="1.0" encoding="utf-8"?>
<updates>
    <update>
        <name>Tallpine Hiking Club package</name>
        <description>Trail reports, conditions and next-hikes module.</description>
        <element>pkg_tallpine</element>
        <type>package</type>
        <version>1.4.1</version>
        <infourl title="Tallpine 1.4.1">https://updates.tallpine.example/releases/1.4.1.html</infourl>
        <changelogurl>https://updates.tallpine.example/changelog.xml</changelogurl>
        <downloads>
            <downloadurl type="full" format="zip">https://updates.tallpine.example/dl/pkg_tallpine-1.4.1.zip</downloadurl>
            <downloadsource type="full" format="zip">https://mirror.tallpine.example/dl/pkg_tallpine-1.4.1.zip</downloadsource>
        </downloads>
        <security>0</security>
        <tags>
            <tag>stable</tag>
        </tags>
        <maintainer>Tallpine web team</maintainer>
        <maintainerurl>https://tallpine.example</maintainerurl>
        <targetplatform name="joomla" version="(5\.[4-9]|6\.[0-9]+)"/>
        <php_minimum>8.3</php_minimum>
        <supported_databases mysql="8.0.0" mariadb="10.6.0" postgresql="14.0"/>
        <sha512>CHANGE_ME_SHA512_OF_THE_ZIP</sha512>
    </update>
</updates>

Identity elements

name is shown in the Update view. element must equal the installed extension’s element (pkg_tallpine, com_tallpine, mod_tallpine_nexthikes; for a plugin, its main file name such as tallpine_conditions) and type the extension type. A plugin additionally needs folder (content, system, …​), and modules and templates need client (site or administrator); for plugins and front-end modules the client defaults to site, so state it in the entry. In the updater classes the client text is converted to the numeric client id with ApplicationHelper::getClientInfo(), and a numeric value is still accepted for backwards compatibility. If any of these identity fields differs from the installed row, the update is never matched and the site simply shows nothing.

version is compared with PHP’s version_compare(), so 1.4.10 is newer than 1.4.9, 1.5.0-rc1 is older than 1.5.0 and an arbitrary suffix has surprising ordering. Keep to major.minor.patch.

targetplatform

targetplatform is the only mandatory filter. name must be joomla (it is compared with the lowercased product name) and version is a regular expression. The 6.2 updater evaluates it as preg_match('/^' . $version . '/', JVERSION): it is anchored at the start but not at the end. Three consequences follow.

  • 5.4 also matches 5.40, and an unescaped dot matches any character. Escape dots and group alternatives: (5\.[4-9]|6\.)` covers 5.4 and later 5.x and every 6.x, and `6\.[0-9] alone targets 6.x only.

  • The pattern must not contain the character /, because it is also the regex delimiter.

  • The Manual documents the optional attributes min_dev_level and max_dev_level, which filter on the third number of the version. The updater classes of the 6.2 source tree do not read them, so a patch-level limit has to be written into the pattern itself, for example 6\.2\.[0-4]$.

Because the file may hold many update entries, a developer who supports several Joomla series publishes one entry per release and series, each with the matching targetplatform, php_minimum and download. The updater keeps the highest version among the entries that pass every filter. Older books such as Developing Extensions for Joomla! 5 (2023) show a pattern that stops at version 5.0 together with Joomla 4-era database minimums; because the match is a regular expression against the running version, that file hides every update from Joomla 5.1 onward and from Joomla 6. Review the pattern at every Joomla minor release, and test it (see the checking section below).

php_minimum and supported_databases

php_minimum is compared with PHP_VERSION using >=. If the site’s PHP is older, the update is not offered for installation and the administrator sees a warning that the version exists but needs a newer PHP. supported_databases carries one attribute per server type (mysql, mariadb, postgresql; the Manual also lists mssql, which core has not supported since 4.0) with a minimum version. The updater looks up the running server type: MariaDB is detected through the version string and its leading 5.5.5- prefix is stripped. If the server type has no attribute at all, the update is also blocked with a message that the database type is not supported, so list every database you really support. When the element is absent, any database is assumed to match. The core minimums of Joomla 6 (the Manual and the upgrade guide disagree on the MariaDB and PostgreSQL floor: 10.4 and 12 in one, 10.6 and 14 in the other) are given in Requirements and hosting; do not declare a lower minimum than your code really works with.

Stability tags and the channel element

A <tags> block holds dev, alpha, beta, rc or stable; only the last tag counts and no tag means stable. The value becomes an integer through stabilityTagToInteger(), and an entry is dropped when its stability is below the site’s Minimum Stability option of the Extensions component. Unknown strings fall back to stable, so a typo such as release-candidate silently publishes a release candidate to everyone. Publish pre-releases in a separate file or entry with an explicit rc tag and use a staging site with a lower minimum stability to test them.

An optional <channel> element restricts an entry to a release channel; when the site has no channel configured or the entry has none, it matches. The channel is used by core’s update site (the Default and Next channels mentioned in Upgrading Joomla 5 to 6); third-party extensions rarely need it.

Download elements

downloadurl has type (full or upgrade) and format (zip, tar, …​). One or more downloadsource elements with the same attributes are mirrors: when the first download fails, the installer tries the next source and appends the download key to each. Keep every URL on one line with no surrounding whitespace, or the installer reports a malformed URL. The archive name should match the URL, and the URL should be HTTPS only.

infourl, changelogurl and the security element

infourl links to a release page and accepts a title. changelogurl links to the changelog XML described next. The optional <security> element is an integer severity (0 None to 4 Critical) that Joomla 6.2 (and 5.4) shows as a badge in the update view and on the dashboard; the displayed value for an extension is the highest among all newer releases. It is information supplied by the update server and not a verdict by the Joomla project. Set it only for releases that really fix a vulnerability, and read Security in extensions for what to publish and when. The mechanism is described from the administrator’s side in the page on extensions and update sites.

Changelogs

The changelog button in the Extensions Manager and the Update view is fed by a separate XML file named in changelogurl, in the manifest (for the Manage screen) and in the update entry (for the Update screen):

<?xml version="1.0" encoding="UTF-8"?>
<changelogs>
    <changelog>
        <element>pkg_tallpine</element>
        <type>package</type>
        <version>1.4.1</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>Dog-friendly tag on trail cards.</item>
        </addition>
        <note>
            <item><![CDATA[Requires <strong>PHP 8.3</strong> or later.]]></item>
        </note>
    </changelog>
</changelogs>

Each changelog needs element, type and version; the change types are security, fix, language, addition, change, remove and note, each with item children. HTML must be wrapped in CDATA. If the file has no entry for the version, the button is not shown, so add the entry to the file in the same release step that publishes the zip.

Checksums, signing and trust

The three optional elements sha256, sha384 and sha512 hold the hex digest of the package. InstallerHelper::isChecksumValid() computes hash_file() for every algorithm present, lowercases and trims the published value and compares: any mismatch aborts the update with an error, all matching gives success, and none present gives only a warning that the package could not be verified. Publish at least sha512 (several can be present and all must match).

# Build the package, then compute the digest published in the update XML
zip -r build/pkg_tallpine-1.4.1.zip pkg_tallpine.xml packages/ script.php
sha512sum build/pkg_tallpine-1.4.1.zip | cut -d' ' -f1

It is important to understand what this does and does not protect. The checksum lives in the same XML as the download link, so it detects a corrupted or swapped zip only if the XML is intact: an attacker who controls the update server or the TLS connection changes both. There is no developer signature that Joomla verifies for third-party extensions; the TUF mechanism is not available to extension update sites, and the installer does not check a package signature. Practical consequences for a developer:

  • serve the XML and the zips over HTTPS with a valid certificate, and keep the update server host hardened and separate from the club website that has many editors;

  • store the digest outside the web root build step (generate the XML in CI rather than editing it by hand), and keep repository and release credentials in CI secrets;

  • sign git tags and release artefacts yourself (for example with GPG or Sigstore) and publish the public key and digest on the project page, so users can verify manually;

  • never reuse a version number for a different zip, since sites cache the update list for the configured cache time;

  • for an administrator, the same facts mean that a missing checksum warning should be taken seriously and that update sites of abandoned extensions should be disabled.

Download keys for paid extensions

A commercial extension adds a dlid element to its manifest, so that the Update Sites screen shows a Download Key field for it:

<!-- com_tallpine.xml (excerpt): premium trail packs -->
<dlid prefix="dlid=" suffix="" />
<updateservers>
    <server type="extension" name="Tallpine Premium">https://updates.tallpine.example/premium.xml</server>
</updateservers>

InstallerHelper::getDownloadKey() reads the prefix and suffix attributes from the installed manifest. When the administrator saves the key, UpdatesiteModel stores prefix + key + suffix in the extra_query column and deletes the cached #__updates rows so the next check applies it. At download time UpdateModel appends extra_query to the downloadurl and to each downloadsource, with ? or & depending on whether the URL already contains a query string. The server therefore sees .../premium.zip?dlid=<key> and can answer with the zip, with 403, or with a redirect.

A good server design for Tallpine’s premium packs:

<?php
// premium.php on the vendor's server: validate the key, then stream the zip
$key = $_GET['dlid'] ?? '';

if (!preg_match('/^[A-Za-z0-9]{32}$/', $key) || !isValidKey($key)) {
    http_response_code(403);
    exit;
}

header('Content-Type: application/zip');
header('Content-Disposition: attachment; filename="com_tallpine-premium.zip"');
readfile(__DIR__ . '/private/com_tallpine-premium.zip');

Validate the format before touching the database, log failures without logging the key, throttle repeated attempts and compare keys against hashed values. The Manual’s Update Servers page does not describe download keys; the source tree is the reference. The key is a credential that lives in the customer’s database and its backups, and it travels in the URL, so it can appear in web server logs: use HTTPS and avoid returning it in redirects. The Update Sites screen can also filter by key state (supported, missing, valid), which helps administrators find sites that never received their key.

Publishing from a pipeline

Hand-edited XML goes stale. The Tallpine club generates the file from the release tag and publishes it to a static host:

# .github/workflows/release.yml (excerpt)
name: release
on:
  push:
    tags: ['v*']
jobs:
  package:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v4
      - name: Build package
        run: |
          mkdir -p build
          zip -r "build/pkg_tallpine-${GITHUB_REF_NAME#v}.zip" pkg_tallpine.xml packages script.php
      - name: Generate update XML
        env:
          VERSION: ${{ github.ref_name }}
        run: |
          V="${VERSION#v}"
          SUM="$(sha512sum "build/pkg_tallpine-$V.zip" | cut -d' ' -f1)"
          sed -e "s/@VERSION@/$V/g" -e "s/@SHA512@/$SUM/g" update.xml.dist > build/pkg_tallpine.xml
      - uses: actions/upload-artifact@v4
        with:
          name: update-site
          path: build/

A later job copies build/ to the update host with a deploy key held in CI secrets. Keep the previous update entries when you regenerate the file (append rather than overwrite) so that sites still on older Joomla series keep seeing their releases.

Checking the file before you publish

curl -sS https://updates.tallpine.example/pkg_tallpine.xml | xmllint --noout - && echo "well-formed"
curl -sSI https://updates.tallpine.example/dl/pkg_tallpine-1.4.1.zip | head -n 1
curl -sS https://updates.tallpine.example/dl/pkg_tallpine-1.4.1.zip | sha512sum

Then install the previous release on a Joomla 6.2 test site (see Local development with Docker), run Check for Updates with the cache cleared and confirm that the update appears, that the changelog button works and that the update installs. Repeat on the oldest Joomla series you claim to support.

Common errors and pitfalls

  • No update appears: element, type, folder or client differ from the installed row; targetplatform does not match the running version; the site’s PHP or database is below the minimum (check for the warning); the stability tag is below the site’s minimum; or the cached list has not expired.

  • "Malformed" or empty download URL: whitespace or a line break inside downloadurl, or an XML entity problem (& must be written &).

  • Checksum errors: the digest was calculated before the zip was rebuilt, or upper and lower case differ in the file name that was hashed.

  • Premium update fails with 403: the key was not saved for that update site, or the prefix and suffix in the manifest do not match what the server expects.

  • Duplicate prompts: the package and its child extensions all declare update servers.

  • Update site missing after a restore: use Rebuild on the Update Sites screen; stored download keys survive it.

  • A new Joomla minor hides updates: the version pattern was written for a closed range of series.

The Joomla Extensions Directory

The JED at https://extensions.joomla.org/ is the official catalogue of extensions; the administrator reaches it from System → Install → Extensions with Install from Web, and the same listings are linked from the User Guide. Listing is voluntary, and the directory is a catalogue and not a store: the download link points to the developer’s own site and the extension is installed through the Joomla installer.

Listing rules

The JED Terms of Service, which every developer accepts, set the rules (section numbers as in the published text):

  • Licence (sections 3.1, 4.1 and 4.2): listed extensions must be GPL-licensed and comply with the latest Joomla licence, with no additional restrictions. Images, CSS and JavaScript need not be GPL, but their use may not be limited. The developer’s site must state the GPL licence and must not imply that being listed is an endorsement.

  • Installer: extensions that do not use the Joomla installer are not listed.

  • Checklist (section 4.1.5): listings must comply with the JED submission checklists, and a non-compliant listing may be unpublished immediately.

  • Paid extensions (sections 4.1 and 4.4): developers may charge for GPL extensions; any extension that requires payment or trade to download, or that bridges to a paid service needed to function, must be listed as paid. Changing between free and paid removes earlier votes and reviews.

  • Maintenance and removal (sections 4.3, 4.4 and 7): out-of-date content or rule breaches can lead to unpublishing; an unpublished listing has 14 days to be corrected before permanent removal; violations lead to suspension of one month, three months and then indefinitely.

The Terms of Service point to two update-related help pages (the update XML file and the Joomla update system requirement) without restating them; the practical rule for a developer is that a listed extension should have a working update server that follows the XML above.

JED Checker

The JED Checker is a Joomla extension that inspects an extension package against the JED standards before submission, covering the files that reviewers look at: the manifest, the presence of an update server, licence headers, direct access guards, use of deprecated or insecure code and similar items. It is installed like any other extension and is useful for packages that are not listed, as a pre-release gate. Run it on the final zip and fix findings before submitting; combine it with the static analysis and security review described in Coding standards and static analysis. Because a checker only looks at patterns, passing it does not mean the code is secure.

Joomla 6 compatibility and the badges

Visitors filter listings by supported Joomla version. At the check date, the Browse Extensions menu offered the filters Compatible with J4, Compatible with J5 and Compatible with J5 (with b/c plugin). The third filter is the one that matters for the move to Joomla 6: an extension that runs on 5.x only because of the backward-compatibility plugin is separated from one that does not need it. Joomla 6.0 removed or relocated APIs (the legacy J* class aliases, \Joomla\CMS\Input, \Joomla\CMS\Filesystem, the JPATH_PLATFORM constant and others) that remain available only through the Behaviour - Backward Compatibility 6 (BC6) plugin, which is a bridge and not a permanent feature (parts of it are slated for removal in Joomla 7). The sensible release policy is:

  1. test the package on 5.4 and 6.2 with BC6 disabled, and declare support only for series you actually tested;

  2. set targetplatform and php_minimum accordingly in the update XML, so that sites on an unsupported combination see a warning instead of a broken update;

  3. record in the changelog when a release requires BC6;

  4. update the JED listing’s version compatibility when a release changes it, since the badge and filters come from what the developer states.

See Deprecations and preparing for Joomla 7 and Versions, release cycle and support for which series are supported, and Upgrading Joomla 5 to 6 for the site-side steps. Older books such as Developing Extensions for Joomla! 5 (2023) describe JED Checker and compatibility labels for Joomla 3 to 5 only.

Security notes

  • A compromised update server can deliver code to every site that trusts it, so protect it like production: MFA on the hosting and registrar accounts, restricted deploy keys, monitoring of the XML for unexpected changes.

  • Never publish a download key, a Joomla secret or deploy credentials in the repository, the XML or the zip; CI reads them from secrets.

  • Keep release notes honest: describe security fixes specifically, and use the <security> severity only when it applies.

  • Remove old releases that contain known vulnerabilities from the public download area, or at least from the update XML.

Version notes

  • The <security> element for updates arrived in Joomla 6.2 and was backported to 5.4.

  • changelogurl is displayed since Joomla 4.0; php_minimum and supported_databases are evaluated by the 6.2 updater as described above.

  • The targetplatform match uses the version of the running Joomla, and Joomla 5.1.1 added the ability for the updater to evaluate the version of a target release (used when checking extensions before a major upgrade).

  • Core updates are TUF-verified since 5.1; extension update sites are not.