Debugging Joomla

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.

Debugging a Joomla extension means answering three questions: what did the page do (queries, language strings, request data, time and memory), where does the code stop and what do the variables hold at that moment, and what did the code tell us while nobody was watching. Joomla answers the first with the Debug System switch and the System - Debug plugin, the second with a step debugger such as Xdebug, and the third with its logging framework. This page covers all three for a developer. The example is the fictitious Tallpine Hiking Club package pkg_tallpine with the component com_tallpine (namespace Tallpine\Component\Tallpine).

It was written against Joomla 6.2.x on PHP 8.3+ and checked on 2026-10-30 against the Joomla Developer Manual, the Joomla User Guide and the 6.2-dev branch of joomla-cms. The administrator-side settings are explained field by field in Global Configuration and System Information, Maintenance and Troubleshooting; the exception style that makes failures visible is in Error Handling and Legacy APIs. PHP’s own tools (var_dump(), error_log(), backtraces, Xdebug modes, profiling) are taught in Debugging and Profiling, and container diagnostics (docker logs, docker exec, docker debug) in Debugging Containers. They are not repeated here; this page shows what is specific to Joomla.

The debugging toolbox at a glance

Question Tool Where it lives

How long did the page take, which queries ran, which language string is missing?

Debug System, Debug Language and the debug console

Global Configuration › System, plugin System - Debug (plg_system_debug)

Why did the request fail with a 500 error?

Error Reporting maximum, the PHP error log, joomla_core_errors.php

Global Configuration › Server and Logging, administrator/logs

What is in this variable at line 120 of the model?

Step debugger (Xdebug, DBGp protocol) in an IDE

PHP extension and an IDE; in Docker a derived image

What happened in production at 03:12?

Logging with Log::add() and a category

Your extension code, administrator/logs/*.php, optionally a syslog or database logger

Which deprecated API does my code still call?

Log Deprecated API (deprecated.php)

Global Configuration › Logging

flowchart LR subgraph switches[configuration.php] D[debug true] L[debug_lang true] E[log_everything,<br/>log_categories,<br/>log_deprecated] end D --> J[JDEBUG constant,<br/>profiler, in-memory logger] D --> P[System - Debug plugin] L --> P J --> P P --> B[Debug console in the page,<br/>php-debugbar] E --> F[Log files in administrator/logs] X[Your code: Log::add] --> LG[Log singleton] LG --> F LG --> M[In-memory logger] M --> P XD[Xdebug in the PHP process] -.-> IDE[IDE on port 9003]

Debug System and the JDEBUG constant

Debug System is the debug property of configuration.php, changed under System › Global Configuration › System › Debug Settings. At the start of every request Joomla turns it into the PHP constant JDEBUG. Core and extensions test that constant, which is why a single switch changes so many behaviours:

  • the application starts a profiler and records time marks (afterRender and others), and the debug console shows them as the Timeline

  • CMSApplication::setupLogging() registers an in-memory logger that collects every log entry of the request, so the console can list them, and lowers the threshold of the core error log from errors only to every priority

  • the System - Debug plugin renders the debug console and collects queries, session, request and memory data

  • extensions that guard expensive diagnostics with if (JDEBUG) run them

The switch is global and affects every visitor’s request, not only yours. The console itself can be limited to some user groups (see below), but the extra data is collected for every request, which costs memory and time, and the verbose error pages are visible to everybody when Error Reporting is maximum. Debug System is therefore a development and staging switch. On the production site of the Tallpine club it stays off and is raised for the minutes it takes to reproduce a problem.

<?php
// configuration.php of the local Tallpine development site
class JConfig
{
    public $debug           = true;      // Debug System: sets JDEBUG
    public $debug_lang      = false;     // Debug Language
    public $debug_lang_const = true;     // markers show the translated text; false shows the language key
    public $error_reporting = 'maximum'; // 'default', 'none', 'simple' or 'maximum'
    public $log_path        = '/var/www/html/administrator/logs';
    // ... remaining properties unchanged
}

Because the properties are plain PHP, the CLI can set them too, which is useful in a container where the administrator is not reachable. Use quotes around values so the shell does not interpret them.

php cli/joomla.php config:set debug=true error_reporting=maximum
php cli/joomla.php config:get debug

When the administrator cannot start at all because of a fatal error, edit configuration.php by hand (it is read-only after installation on most hosts, so the permissions must be changed temporarily), reproduce the error, read the stack trace, and revert both the values and the permissions. An error that appears only with Debug System on is often a deprecation promoted to a visible message by maximum; read it before concluding that Debug System caused the problem.

The System - Debug plugin and the debug console

Debug System alone draws nothing. The console is produced by the System - Debug plugin, in the extensions list as plg_system_debug (element debug, group system, namespace Joomla\Plugin\System\Debug). The plugin is enabled in a default installation. Since Joomla 4 its console is built on the open-source library php-debugbar: the plugin creates a DebugBar\DebugBar object, adds Joomla-specific data collectors to it, and renders the bar with a Joomla subclass of the library’s JavaScript renderer. The 6.2 composer.json requires php-debugbar/php-debugbar at ^2.2.6. The library’s class names matter when you read the source or when a Composer update changes the bar’s behaviour; the Joomla-specific collectors live in the plugin’s src/DataCollector folder.

Collector class Console tab and content

InfoCollector

Joomla information: version, PHP version, database driver, request time, the profile of the application marks

SessionCollector

Session: the session registry and the token

RequestDataCollector

Request: GET, POST, cookies and server variables of the current request

ProfileCollector

Profile and the timeline: each profiler mark with elapsed time and memory

MemoryCollector

Memory: peak memory use

QueryCollector

Queries: every SQL statement with its duration, duplicates, optional call stack, profile and EXPLAIN

LanguageFilesCollector, LanguageStringsCollector, LanguageErrorsCollector

Language: files loaded, strings used (with the file and line that asked for them) and strings that were not found

UserCollector

User: the identity of the current user

MessagesCollector (from the library)

Log: the entries held by the in-memory logger, mapped to message levels

Plugin options

The plugin’s three tabs map to the fields of debug.xml:

Field (parameter) Default Effect

Allowed Groups (filter_groups)

none

User groups that may see the console. Empty means every visitor, guests included.

Refresh Assets (refresh_assets)

on

With Debug System on, re-evaluates the Web Asset Manager files on each request so a rebuilt CSS or JavaScript file is served at once. See Web Asset Manager.

Memory, Request, Session, Profile, Queries (memory, request, session, profile, queries)

show

Which tabs are drawn.

Query Traces, Query Profiles, Query Explains (query_traces, query_profiles, query_explains)

off

A PHP call stack for each query; the database’s per-statement profile (MySQL SHOW PROFILE, which reports an error if the server has profiling disabled); the EXPLAIN of each SELECT. They add queries of their own.

Track Request History (track_request_history)

off

Stores the data of recent requests in a file store below the cache folder (plg_system_debug_ followed by the application name) so that the bar can reopen the data of earlier requests, including Ajax requests, through an open handler.

Language Errors, Language Files, Language Strings (language_errorfiles, language_files, language_strings)

show

The three language tabs. Strip First, Strip Prefix and Strip Suffix shorten the displayed keys.

Log Entries (logs) and Log Deprecated Core (log-deprecated-core)

on, off

Whether log entries appear in the bar, and whether deprecations raised by core code (not only by extensions) are listed.

How the console is drawn

The plugin is a SubscriberInterface plugin (see Plugin Development and Events) subscribed to onBeforeCompileHead, onBeforeRespond, onAfterRespond, onAjaxDebug and the database onAfterDisconnect event. In outline:

  1. onBeforeCompileHead adds the bar’s script and stylesheet to the page, but only when JDEBUG (or Debug Language) is on, the current user passes the Allowed Groups check and the document is HTML.

  2. onBeforeRespond copies the profiler marks into the bar.

  3. onAfterRespond collects everything else and inserts the rendered bar into the response body. Ajax requests are skipped, except for request history.

  4. When the database connection closes (onAfterDisconnect) the plugin runs the optional EXPLAIN and profile queries, so that diagnostic queries are not counted as the page’s own.

Two consequences follow. A response that is not an HTML document (JSON from the Web Services API, a raw format=raw view, a download) has no console; read the data in the log or with request history instead. And because the bar is inserted after rendering, a fatal error that stops the page earlier means no bar at all: the PHP error log and joomla_core_errors.php are the places to look.

Reading the Queries tab

The Queries tab is the quickest way to see the cost of a model. A list view of com_tallpine that loads each trail’s category in a loop shows as the same SELECT repeated with different identifiers; the tab flags duplicated statements and sums the time. The fix is to join the category once in getListQuery() (see Database API). Turn on Query Explains to see whether the statement uses an index and Query Traces to find the line of your model that issued it. Statements are shown with their bound values substituted, which means the tab also reveals data; this is one more reason to restrict the console.

Security of the console

The console exposes the SQL of the site, the request, the session, file paths and the language files. If Allowed Groups is empty and Debug System is on, every anonymous visitor sees it. A public site therefore follows three rules: keep Debug System off; if it must be on, select Super Users in Allowed Groups; and never leave maximum error reporting on. The Ajax endpoint that serves request history requires a valid form token, so it cannot be fetched by a script that has no session. Also remember that the in-memory logger and the bar hold log messages: do not write secrets or complete personal data in a log message at any priority.

Debug Language

Debug Language (debug_lang) is a different tool with a similar name. It changes how the language layer returns strings, so that you can see where each visible text comes from. With it on, Text::_('KEY') returns:

  • **text** when the key exists: the text is wrapped in two asterisks on each side. With the default Debug Language Constant setting (debug_lang_const true) the text shown is the translation; set it to No to show the language key (COM_TALLPINE_TRAIL_DISTANCE_LABEL) instead.

  • ??KEY?? when the key is not found, wrapped in question marks, usually showing the key that was requested.

With Debug Language on the debug console is also drawn (the plugin treats Debug Language as a reason to load the bar), and its Language tabs list the files that were loaded, the strings that were used with the file and line that asked for them, and the orphans. This is the tool for the German version of the Tallpine site: switch to German, turn on Debug Language, and every ??...?? is a missing entry in de-DE/com_tallpine.ini.

; language/en-GB/com_tallpine.ini
COM_TALLPINE_TRAIL_DISTANCE_LABEL="Distance (km)"
COM_TALLPINE_TRAIL_ELEVATION_LABEL="Elevation gain (m)"
<?php
use Joomla\CMS\Language\Text;

// With debug_lang on and the German file lacking the key, this prints ??COM_TALLPINE_TRAIL_DIFFICULTY_LABEL??
echo Text::_('COM_TALLPINE_TRAIL_DIFFICULTY_LABEL');

Typical findings and their causes:

  • A **text** that never appears in a template means the text is hard-coded or comes from another source (a module title, article content), not from a language file.

  • A ??KEY?? that is correct in English but missing in German means the file was not loaded for that language: check that Factory::getApplication()->getLanguage()->load('com_tallpine', ...) or the extension’s own loading is reached, and that the file name matches the extension element.

  • Strings in .ini files that contain unescaped double quotes or reserved words make the parser fail and are listed on the Language Errors tab. Debug Language also makes the parser report such files more strictly.

  • Strings inside an HTML attribute (a placeholder, a title) are broken by the asterisks, which is expected: check them in the console list instead of reading the markup.

Turn Debug Language off before taking screenshots or running end-to-end tests that compare text, since every string is altered. Language files and their loading are explained in Language Files and Internationalisation.

Error reporting and the PHP error log

The page you see is only one of the places where an error can be reported. Joomla’s Error Reporting option maps to PHP’s error_reporting for the request: default leaves the server’s value, none silences everything, simple reports errors and warnings, and maximum reports everything including notices and deprecations. The PHP setting display_errors decides whether messages go to the page; log_errors and error_log decide whether they go to a file. In a container, send them to standard error so docker compose logs shows them. The official image’s PHP uses php.ini values you can override with a file in conf.d.

; conf.d/50-tallpine-dev.ini (development only)
display_errors = On
log_errors = On
error_log = /proc/self/fd/2
error_reporting = E_ALL

The Joomla exception handler renders the error page of the active template (error.php); with Debug System on or Error Reporting maximum, the page also includes the exception class, the file and line and the trace. See Error, Offline and Component Pages for customising that page and for why a public site must not show a trace. In production keep errors in the log and show the generic page.

Step debugging with Xdebug

Step debugging stops PHP on a line you choose and lets you inspect variables, evaluate expressions and walk the call stack, which is the right tool for a controller that redirects to the wrong place or a form filter that changes a value. The mechanism (the debug mode, port 9003, trigger cookies, xdebug_info()) is explained in Debugging and Profiling; this section adds the Joomla and Docker specifics.

The official joomla image does not include Xdebug, and Xdebug slows every request, so build a development image on top of it and keep it out of production. Joomla’s code lives in /var/www/html inside the image.

# docker/Dockerfile.dev
FROM joomla:6.2-php8.4-apache

# Build tools are needed only while compiling the extension.
RUN apt-get update \
 && apt-get install -y --no-install-recommends $PHPIZE_DEPS \
 && pecl install xdebug-3.5.3 \
 && docker-php-ext-enable xdebug \
 && apt-get purge -y --auto-remove $PHPIZE_DEPS \
 && rm -rf /var/lib/apt/lists/*

COPY docker/xdebug.ini /usr/local/etc/php/conf.d/99-xdebug.ini
; docker/xdebug.ini
xdebug.start_with_request=trigger
xdebug.client_host=host.docker.internal
xdebug.client_port=9003
xdebug.log_level=0

The ini file deliberately leaves out xdebug.mode: the XDEBUG_MODE environment variable of the container sets it, and it overrides the ini setting. Extend the Compose file from Local Development with Docker with a build section, the variable, the host mapping for Linux hosts and a bind mount of the extension source so edits appear without rebuilding:

# compose.override.yaml
services:
  joomla:
    build:
      context: .
      dockerfile: docker/Dockerfile.dev
    environment:
      XDEBUG_MODE: develop,debug
      XDEBUG_CONFIG: client_host=host.docker.internal client_port=9003
    extra_hosts:
      - "host.docker.internal:host-gateway"
    volumes:
      - ./extensions/com_tallpine/admin:/var/www/html/administrator/components/com_tallpine
      - ./extensions/com_tallpine/site:/var/www/html/components/com_tallpine

Then start the stack with docker compose up -d --build. Docker Desktop resolves host.docker.internal itself; the extra_hosts line makes it work on Linux. Xdebug opens the connection from the container to your IDE, so the IDE listens on port 9003 and no port needs to be published for it; a host firewall must allow the incoming connection from the Docker network. xdebug_info() or php --ri xdebug inside the container, run with docker compose exec joomla, shows the active mode and the result of the last connection attempt.

The IDE must translate container paths into project paths. A bind mount makes this exact for the extension, while the rest of Joomla (in the volume) can be mapped to a copy of the core that you keep for reading. A VS Code launch.json for the PHP Debug extension:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Listen for Xdebug (Tallpine)",
      "type": "php",
      "request": "launch",
      "port": 9003,
      "pathMappings": {
        "/var/www/html/administrator/components/com_tallpine": "${workspaceFolder}/extensions/com_tallpine/admin",
        "/var/www/html/components/com_tallpine": "${workspaceFolder}/extensions/com_tallpine/site"
      }
    }
  ]
}

PhpStorm uses the same idea under Settings › PHP › Servers with Use path mappings and the server name equal to the PHP_IDE_CONFIG variable (serverName= followed by the server name) if you set one.

With start_with_request=trigger, a request starts a session only when it carries the trigger. In a browser, set the XDEBUG_SESSION cookie with a browser extension or add ?XDEBUG_TRIGGER=1 to the URL; Joomla’s SEF router and the form token are unaffected. For the CLI (the tallpine:import command from Console Command Plugins) pass the variables to the process:

docker compose exec -e XDEBUG_MODE=debug -e XDEBUG_TRIGGER=1 --user www-data \
  joomla php cli/joomla.php tallpine:import --dry-run

Where to put breakpoints

  • Dispatcher::dispatch() of the component (src/Dispatcher/Dispatcher.php) is where the request enters the extension.

  • BaseController::execute() and the task method (save(), publish()) show which task was resolved and whether the token check passed. See Component MVC: The Administrator Side.

  • AdminModel::save() and Table::store() are where data is filtered, validated and written.

  • A plugin’s event handler method (onContentPrepare() for a content plugin) shows whether the event reached it; put the breakpoint in the subscriber itself (see Plugin Development and Events).

Pitfalls

  • The IDE never receives a connection. Check client_host, that the IDE listens on 9003 (Xdebug 2 used 9000), the firewall, and that the request has the trigger.

  • Breakpoints are grey. The path mapping is wrong; the debugger sees paths below /var/www/html and cannot find the file.

  • Everything is slow. Xdebug is loaded in develop mode on every request. Use XDEBUG_MODE=off for routine work and switch to debug when needed.

  • Timings look wrong. Time spent stopped at a breakpoint is counted by the profiler, so read the Profile tab only from runs without a debugger attached.

  • Caching hides your change. Clear the cache, or turn off the Joomla cache while debugging; see Caching and Performance. OPcache with a bind mount can serve stale code when opcache.validate_timestamps is off.

  • Never install Xdebug on a production host or in the image you publish. It can expose a debugging port to anyone who can reach the server and slows every request.

Logging with Log::add()

Debugging tools help while you watch; logs help when you are not there. Joomla’s logging framework is the class Joomla\CMS\Log\Log, a singleton that routes log entries to loggers according to the entry’s priority and category.

<?php
use Joomla\CMS\Log\Log;

Log::add('Closure sync failed for trail 17', Log::ERROR, 'com_tallpine');

The signature in 6.2 is Log::add($entry, $priority = Log::INFO, $category = '', $date = null, array $context = []). The first argument is the message (a string) or a ready LogEntry; the date defaults to now; the optional $context array carries extra data for loggers that use it.

Priorities

Priorities are bit flags that follow the severities of RFC 5424 (syslog), defined as constants of Log:

Constant Value Use for

Log::EMERGENCY

1

The site is unusable.

Log::ALERT

2

Action must be taken at once.

Log::CRITICAL

4

A component failed, such as the park-authority API being unreachable for the closure sync.

Log::ERROR

8

An operation failed and the user saw an error.

Log::WARNING

16

Something unexpected that the code handled.

Log::NOTICE

32

Normal but significant events.

Log::INFO

64

Routine information. This is the default priority of Log::add().

Log::DEBUG

128

Detail for developers.

Log::ALL

30719

Every priority.

Because they are flags, several priorities are combined with + or | when registering a logger.

Categories

The category is free text. Use the extension’s element name (com_tallpine, plg_task_tallpine_trailsync) so that your entries do not collide with the others, and use a sub-category when one extension has several concerns (com_tallpine.sync). Categories are what the Global Configuration options and addLogger() filter on. Core uses some: system goes to joomla_core_errors.php, deprecated to the deprecation log, databasequery to database logging, and jerror is special: an entry with the category jerror is also shown to the user in the message area, in addition to being logged. The Debug console collects every category.

Guard debug messages

Detailed messages at Log::DEBUG should cost nothing when debugging is off. The Manual wraps them in a JDEBUG check:

<?php
if (JDEBUG) {
    Log::add(sprintf('Syncing %d closures', \count($closures)), Log::DEBUG, 'com_tallpine.sync');
}

Which files exist and who creates them

In 6.2 the application’s setupLogging() registers these loggers, depending on Global Configuration, and your code can add more:

File in the log folder Written when Contains

joomla_core_errors.php

always

Entries in the system category. Errors and above, or every priority when Debug System is on.

deprecated.php

Log Deprecated API is on

Entries in the deprecated category.

everything.php

Log Almost Everything is on

Every entry except the deprecation categories (deprecated, deprecation-notes) and databasequery.

custom-logging.php

Log Priorities and Log Categories are set

Only entries matching them. Log Category Mode decides whether the listed categories are included or excluded. The categories are split at any character other than letters, digits, underscore, dot and hyphen.

the name you give

your code calls Log::addLogger()

What you route to it.

Custom logging with Global Configuration needs no code: put com_tallpine in Log Categories, choose the priorities, set the mode to Include, and custom-logging.php collects only the Tallpine entries. An administrator can turn it off, which is why an extension that depends on its log should register its own logger.

Registering your own logger

Log::addLogger($options, $priorities, $categories, $exclude) takes an options array, the priorities to accept, the categories to include (or to exclude when the fourth argument is true). Register loggers early, for example in the extension’s boot() method or the plugin’s constructor, and only once per request.

<?php
namespace Tallpine\Component\Tallpine\Administrator\Extension;

use Joomla\CMS\Extension\BootableExtensionInterface;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\Log\Log;
use Psr\Container\ContainerInterface;

final class TallpineComponent extends MVCComponent implements BootableExtensionInterface
{
    public function boot(ContainerInterface $container): void
    {
        // Our own file, errors and worse, only the com_tallpine category.
        Log::addLogger(
            [
                'text_file'         => 'com_tallpine.log.php',
                'text_entry_format' => '{DATETIME} {PRIORITY} {CATEGORY} {MESSAGE}',
            ],
            Log::ERROR | Log::CRITICAL | Log::ALERT | Log::EMERGENCY,
            ['com_tallpine']
        );
    }
}

The options of the text-file logger:

  • text_file: the file name. It defaults to error.php.

  • text_file_path: the folder; the default is the configured log folder.

  • text_file_no_php: when false (the default) the file carries the .php extension and begins with a line that stops the file from being served when someone requests it by URL. Set it to true only if the folder is not web-accessible and another tool must read the file.

  • text_entry_format: the line template. The default is a tab-separated {DATETIME}\t{PRIORITY}\t{CLIENTIP}\t{CATEGORY}\t{MESSAGE}; other placeholders are {TIME} and {DATE}.

  • defer: collects entries and writes them at the end of the request.

The priorities argument above uses |; the Manual’s example adds the constants with `, which gives the same result for distinct flags. Do not add a priority twice with `, which would produce a different number.

Other loggers are selected with the logger option: messagequeue shows entries in the message area, echo prints them, inmemory keeps them for the request, and there are syslog, database, callback, w3c, formattedtext and others in libraries/src/Log/Logger. A custom logger class is registered with Log::registerLogger($key, $class) and must extend Joomla\CMS\Log\Logger.

<?php
use Joomla\CMS\Log\Log;

// Show warnings of the sync in the administrator message area during development.
Log::addLogger(['logger' => 'messagequeue'], Log::WARNING | Log::ERROR, ['com_tallpine.sync']);
Log::add('Closure feed returned 0 items', Log::WARNING, 'com_tallpine.sync');

PSR-3

Log::createDelegatedLogger() returns an object that implements Psr\Log\LoggerInterface, so code and libraries written against the PSR-3 interface log through Joomla’s routing. A PSR-3 level such as critical() is mapped to the matching priority, and the category is read from the context array.

<?php
use Joomla\CMS\Log\Log;

$logger = Log::createDelegatedLogger();
$logger->critical('Park authority API unreachable', ['category' => 'plg_task_tallpine_trailsync']);

Logging pitfalls and security

  • Log::add() can throw. If the file cannot be written (permissions, a full disk) the logger raises an exception. In a Docker volume owned by root the log folder is not writable by www-data; fix the ownership instead of catching everything.

  • Logger registered too late. An entry added before a matching logger exists is dropped. Register loggers in boot() or in the plugin constructor, not in a template.

  • Excluded category mode. With Category Mode set to exclude, the categories listed are not logged, and everything else is, which is the opposite of what many administrators expect.

  • Files grow without limit. Joomla does not rotate its log files. Rotate them with logrotate or a container log driver, or log to standard error from a custom logger in a container.

  • Secrets and personal data. Do not log passwords, tokens, the session id or complete form submissions. Log the identifier (trail 17), not the member’s data. Log files contain client IP addresses, which are personal data under the GDPR; see Privacy and Action Logs.

  • Log injection. A message built from user input can add fake lines. Strip newlines and control characters from values that come from requests.

  • Keep the folder outside the web root in a hardened install, as Global Configuration suggests, and rely on the .php guard as a second line of defence only.

  • Logs are not the audit trail. The Action Logs component records what users did; it is a different, user-facing feature, covered in the same privacy page.

A debugging workflow for com_tallpine

  1. Reproduce the problem locally in the Compose stack, with Debug System on, Allowed Groups set to Super Users and Error Reporting maximum.

  2. Read the Debug console: the Queries tab for slow or duplicated statements, the Language tab for missing keys, the Log tab for entries that code already wrote.

  3. If the console is not enough, start Xdebug with the trigger, set a breakpoint at the controller task and step towards the failing line.

  4. Add a Log::add() call with the extension’s category where the failure would have been visible, and keep it: a production failure will then leave a line.

  5. Run with Log Deprecated API on and fix every entry that names your files before moving to the next major version; see Deprecations and Preparing for Joomla 7.

  6. Turn Debug System and Debug Language off and verify the fix once more with production settings.

Where older books and tutorials are outdated

Older material about debugging Joomla was written for earlier versions, and some of it is now misleading:

  • Joomla 3 tutorials call the debugger Debug Mode and describe a console built into the plugin as a table at the foot of the page; since Joomla 4 the console is a php-debugbar bar with tabs, and the plugin’s options are different.

  • Books and wiki pages written for Joomla 3 use JLog::add(), JFactory::getApplication()->enqueueMessage() and JDEBUG through the J classes. In Joomla 6 use Joomla\CMS\Log\Log; the J class aliases are deprecated and log a deprecation (see Error Handling and Legacy APIs).

  • Xdebug 2 settings (xdebug.remote_enable, xdebug.remote_host, xdebug.remote_port 9000) were replaced in Xdebug 3 by xdebug.mode, xdebug.client_host and xdebug.client_port 9003. Tutorials with the old names do nothing in current versions.

  • Older tutorials turn on Debug System and leave it on. The User Guide and the Manual both position it as a temporary tool, and a public site should restrict the console to Super Users.

  • Developing Extensions for Joomla! 5 (Packt, 2023) and Joomla! 4 Masterclass (Packt, 2023) cover debugging only briefly; for Joomla 5.1 and later the Manual’s logging page, the Joomla 6 source and the Xdebug documentation are the authoritative sources.

  • The legacy wiki at docs.joomla.org still has debugging pages for Joomla 1.5 to 3; the current User Guide and Manual supersede them.