| name | joomla |
| description | Joomla 5+ / 6 / 7 extension development for components, modules, plugins, libraries, and templates using modern MVC with PSR-4, DI, and service providers. Trigger on Joomla 5, 5.4, 6, 6.1, 6.2, 7, J5/J6/J7, Joomla CMS, provider.php, manifest XML, install script, scriptfile, or any Joomla extension code. Covers scaffolding; MVC views/models/controllers; service providers; manifests; database migrations; language files; form fields; layout and template overrides; plugin event subscribers (SubscriberInterface, CMSPlugin); module dispatchers; Web Asset Manager; task / webservices (Joomla JSON:API) / finder / schemaorg plugins; SEF routers. Use for prompts like "add a view", "register web assets", "override a layout" in any Joomla project. |
Joomla 5+ Extension Development
This skill guides you through building Joomla 5+ extensions (components, modules, plugins) using modern architecture patterns derived from the joomla-cms core and real-world production components.
Target: Natively Joomla 6, backward compatible with Joomla 5 (no backward compatibility plugin required)
PHP requirement (Joomla 6.x): 8.3+ minimum and supported, 8.4 recommended (source)
Coding standard: PSR-12 (PHP), Joomla ESLint config (JavaScript)
Canonical sources
When a Joomla pattern is non-obvious, ambiguous, or might have drifted between versions, verify against upstream before answering. Prefer fetching directly with WebFetch; if the fetch is blocked, fails, or the user is offline, fall back in this order: (1) cite the canonical URL so the user can open it, (2) rely on the patterns documented in this skill and references/*.md, (3) ask the user to paste the relevant snippet rather than guess.
Primary — source of truth for runtime behavior:
-
github.com/joomla/joomla-cms — core CMS. Active dev branches (verified 2026-04-30, after J6.1 release):
6.1-dev — current released J6 line, in patch maintenance. Default reference for new J6 code.
6.2-dev — next J6 minor in development.
5.4-dev — current released J5 line.
7.0-dev — next major in development. Where deprecations land for removal (e.g., the CMSPlugin::__construct(DispatcherInterface, …) deprecation slated for removal here — see references/plugin.md).
Pick the branch matching the target Joomla version for the code you're verifying. Re-check /branches periodically — Joomla cuts new minor branches frequently and the names above will drift.
-
github.com/joomla-framework — standalone Framework packages (DI, Event, Filesystem, etc.) reused by the CMS.
Documentation:
- manual.joomla.org — current Developer Manual (Joomla 5+). Preferred prose reference.
- Source repo:
github.com/joomla/Manual (default branch main) — the Markdown backing the rendered site. Useful when you need to grep, fetch raw, or cite a permalink to a specific page; manual edits / corrections also go here as PRs.
- api.joomla.org — generated API reference (classes, methods, signatures).
- framework.joomla.org — Joomla Framework package docs.
- docs.joomla.org — legacy wiki. Useful for historical context (J3/J4) but often stale for J5/J6; cross-check against
joomla-cms HEAD before quoting.
When citing in generated code or replies: prefer a permalink to a specific file/line in joomla-cms (right-click → "Copy permalink" produces a commit-pinned URL) over a branch link, so the reference does not silently drift.
Update cadence: if a WebFetch reveals the upstream pattern differs from what this skill teaches, flag it to the user and recommend opening an issue on Joomla-Bible-Study/claude-skill-joomla so the skill can be corrected.
Coding Standards
Standards: PSR-12 for PHP, Joomla ESLint flat config for JavaScript, joomla/coding-standards PHPCS ruleset, C++ style (//) for inline comments. Every PHP file ships a @package / @copyright / @license header, every class/property/method gets a docblock with @since, @param blocks align to two-space minimum spacing, and @return is always required. For in-flight code where the next release version isn't fixed yet, use the literal @since __DEPLOY_VERSION__ placeholder — the release build substitutes it with the actual version before the package is archived. JS sources live in build/media_source/ and compile to media/<extension>/js/; the Joomla and bootstrap globals are pre-registered. For full docblock examples (file header, class, property, method, deprecated, __DEPLOY_VERSION__ placeholder), JS conventions and JSDoc patterns, the PHPCS install / phpcs.xml template, and the inline-comment style rules, read references/coding-standards.md.
Quick Start: Which Extension Type?
Before writing code, identify the right extension type:
- Component — Full application with admin backend + frontend views, database tables, CRUD operations, menus. Use when you need a complete management interface (e.g., a booking system, directory, inventory tracker, content manager).
- Module — Small, self-contained display block (sidebar, footer, header widget). Has a dispatcher, optional helper, and template. No admin CRUD — gets data from components or its own parameters.
- Plugin — Event-driven code that hooks into Joomla's lifecycle. Types include: content, system, finder (search), task (scheduled), webservices (API), schemaorg, and more.
- Library — Shared PHP code used by multiple extensions. Installed to
libraries/ and autoloaded via PSR-4 namespace. Use when you have utility classes, API wrappers, or shared logic consumed by your components, plugins, and modules.
- Template — Controls the site's HTML shell and layout overrides.
For detailed patterns of each type, read the appropriate reference file:
references/component.md — Full component architecture
references/module.md — Module structure and dispatcher pattern
references/plugin.md — Plugin event subscriber pattern
references/library.md — Library structure and packaging
Cross-cutting references (loaded on demand):
references/admin-routing.md — task= vs view= URL patterns and the checkout pattern that prevents concurrent overwrites
references/coding-standards.md — PSR-12 / PHPDoc / ESLint / PHPCS conventions
references/component-advanced.md — Toolbar API, batch, ordering, tags, versioning, workflow, webservices, mail templates, dashboards, custom rules
references/component-lifecycle.md — Model save flow, prepareTable(), Table::bind()/store(), filter forms, install script, config.xml, site-side differences, HTMLHelper services, showon and fieldset tabs
references/database.md — Install SQL / update SQL, #__ prefix, DDL-vs-DML rule
references/editor-api.md — WYSIWYG editor JS/PHP API + XTD buttons
references/form-fields.md — Built-in field types + custom field authoring
references/layouts.md — LayoutHelper::render(), override priority, sublayouts, key built-in layouts
references/menu-items.md — Site-view menu item type XML (request fields, params, useglobal, multi-layout)
references/packaging.md — Manual zip, build scripts, package extensions, include/exclude checklist, changelog XML
references/testing.md — PHPUnit + Jest patterns with real Joomla CMS classes
references/update-server.md — Update server XML, <targetplatform> regex, SHA hashes, per-type tweaks
references/web-assets.md — joomla.asset.json schema, useStyle/useScript, dependencies
references/gotchas.md — Hard-won J5/J6 pitfalls (controllers, routing, WAM, dark mode, etc.)
Shared cross-extension references (linked from the per-type files above):
references/manifest.md — Universal manifest elements (<extension> root, metadata, <files>, <media>, <languages>, <scriptfile>, <update> / <updateservers>)
references/install-script.md — Lifecycle hooks (preflight/install/update/postflight/uninstall) shared by component/module/plugin/library
references/language-files.md — Filename conventions, key prefixes per type, plurals, Text::script() JS registration
references/service-provider.md — ServiceProviderInterface pattern + per-type binding table (component/module/plugin)
references/component-router.md — Router class + RouterServiceInterface + RouterFactory 3-part contract and SEF rules
Core Architecture Principles
1. PSR-4 Namespaces (Everything Lives in src/)
All PHP classes go under src/ and use PSR-4 autoloading. Joomla maps namespaces declared in the manifest XML.
Component namespace declaration (manifest XML):
<namespace path="src">Vendor\Component\MyComponent</namespace>
Joomla auto-expands this to:
Vendor\Component\MyComponent\Administrator\ → admin/src/
Vendor\Component\MyComponent\Site\ → site/src/
Vendor\Component\MyComponent\Api\ → admin/src/Api/ (if present)
Example:
<namespace path="src">Acme\Component\Bookings</namespace>
Acme\Component\Bookings\Administrator\Model\BookingModel → admin/src/Model/BookingModel.php
Acme\Component\Bookings\Site\View\Booking\HtmlView → site/src/View/Booking/HtmlView.php
Module namespace:
<namespace path="src">Vendor\Module\MyModule</namespace>
Plugin namespace:
<namespace path="src">Vendor\Plugin\PluginGroup\MyPlugin</namespace>
Case sensitivity matters on Linux. Directory names and class names must match exactly.
Joomla 5/6 Compatibility Note: createQuery() and \Joomla\Input\Input work on both Joomla 5 and 6. Writing code this way means it runs natively on J6 without the backward compatibility plugin, while also working fine on J5.
2. Service Provider (The Modern Entry Point)
Every extension has services/provider.php — this is how Joomla discovers and bootstraps the extension through its DI container. The wrapping pattern (ServiceProviderInterface + anonymous class + register()) is shared across components, modules, and plugins; for the universal pattern, the per-type binding table, and the common DI pitfalls see references/service-provider.md. The example below is the component flavor — it registers MVCFactory, ComponentDispatcherFactory, RouterFactory, and (when applicable) CategoryFactory, then binds ComponentInterface.
Component service provider pattern:
<?php
\defined('_JEXEC') or die;
use Vendor\Component\MyComponent\Administrator\Extension\MyComponentComponent;
use Joomla\CMS\Component\Router\RouterFactoryInterface;
use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
use Joomla\CMS\Extension\ComponentInterface;
use Joomla\CMS\Extension\Service\Provider\CategoryFactory;
use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory;
use Joomla\CMS\Extension\Service\Provider\MVCFactory;
use Joomla\CMS\Extension\Service\Provider\RouterFactory;
use Joomla\CMS\HTML\Registry;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
return new class () implements ServiceProviderInterface {
public function register(Container $container): void
{
$container->registerServiceProvider(new CategoryFactory('\\Vendor\\Component\\MyComponent'));
$container->registerServiceProvider(new MVCFactory('\\Vendor\\Component\\MyComponent'));
$container->registerServiceProvider(new ComponentDispatcherFactory('\\Vendor\\Component\\MyComponent'));
$container->registerServiceProvider(new RouterFactory('\\Vendor\\Component\\MyComponent'));
$container->set(
ComponentInterface::class,
function (Container $container) {
$component = new MyComponentComponent(
$container->get(ComponentDispatcherFactoryInterface::class)
);
$component->setRegistry($container->get(Registry::class));
$component->setMVCFactory($container->get(MVCFactoryInterface::class));
$component->setRouterFactory($container->get(RouterFactoryInterface::class));
return $component;
}
);
}
};
The namespace string passed to each factory (e.g., '\\Vendor\\Component\\MyComponent') tells Joomla where to find your Controller, Model, View, and Table classes. Get this wrong and nothing loads.
3. Modern Joomla API — What to Use and What to Avoid
This is critical. Code must work natively on Joomla 6 WITHOUT the "Behaviour - Backward Compatibility 6" plugin, and also run on Joomla 5.
Use these (Joomla 6 native):
| Task | Modern API |
|---|
| Database access | $this->getDatabase() or inject DatabaseInterface |
| Build queries | $db->createQuery() (preferred over $db->getQuery(true)) |
| Current user | $this->getCurrentUser() in models, $this->getIdentity() in views/controllers |
| Input handling | \Joomla\Input\Input (NOT \Joomla\CMS\Input) — CMS\Input is removed in J6 |
| Create model/table | MVCFactory $this->getMVCFactory()->createModel('Name') |
| Input in controllers | $this->input (already available, uses \Joomla\Input\Input) |
| Error handling | Throw exceptions (\RuntimeException, \InvalidArgumentException) |
| Web assets | WebAssetManager via $wa = $this->getDocument()->getWebAssetManager() |
| File operations | PHP native functions or Symfony Filesystem (NOT \Joomla\CMS\Filesystem) |
| getItem() return | Treat as stdClass (NOT CMSObject) — no ->get() or ->set() magic |
Never use these (removed or behind compat plugin in Joomla 6):
| Deprecated | Why | Replacement |
|---|
getDbo() or $this->_db | Removed in Joomla 5 | $this->getDatabase() |
$db->getQuery(true) | Still works but deprecated pattern | $db->createQuery() |
\Joomla\CMS\Input | Removed in Joomla 6 | \Joomla\Input\Input |
\Joomla\CMS\Filesystem | Moved to compat plugin in J6, removed J7 | PHP native / Symfony |
CMSObject properties via ->get() / ->set() | getItem() returns stdClass in J6 | Direct property access: $item->title |
Factory::getUser() | Deprecated | $this->getCurrentUser() or $this->getIdentity() |
getSession()->get('user') | Use identity methods | $this->getIdentity() |
getError() / setError() on models (BaseDatabaseModel) | Use exceptions | Throw \RuntimeException |
new ClassName() for models | Hard-coded dependencies | $this->getMVCFactory()->createModel() |
jimport() | Removed | PSR-4 autoloading |
CMSObject class | Deprecated, removed in J7 | stdClass or custom classes |
getErrorMsg() | Removed | Throw exceptions |
4. Directory Structure
Component layout:
com_mycomponent/
├── mycomponent.xml # Manifest
├── mycomponent.script.php # Install/update script (optional)
├── admin/
│ ├── services/
│ │ └── provider.php # DI container registration
│ ├── src/
│ │ ├── Extension/
│ │ │ └── MyComponentComponent.php
│ │ ├── Controller/
│ │ │ ├── DisplayController.php
│ │ │ ├── ItemController.php # FormController (single item)
│ │ │ └── ItemsController.php # AdminController (list)
│ │ ├── Model/
│ │ │ ├── ItemModel.php # FormModel (single)
│ │ │ └── ItemsModel.php # ListModel (list)
│ │ ├── View/
│ │ │ ├── Item/
│ │ │ │ └── HtmlView.php
│ │ │ └── Items/
│ │ │ └── HtmlView.php
│ │ ├── Table/
│ │ │ └── ItemTable.php
│ │ ├── Field/ # Custom form fields
│ │ ├── Helper/ # Utility classes
│ │ ├── Dispatcher/
│ │ │ └── Dispatcher.php
│ │ └── Service/
│ │ └── HTML/ # HTMLHelper services
│ ├── forms/ # XML form definitions
│ ├── sql/
│ │ ├── install.mysql.utf8.sql
│ │ └── updates/
│ │ └── mysql/
│ │ └── 1.0.0.sql
│ ├── tmpl/ # Admin templates
│ │ ├── item/
│ │ │ └── edit.php
│ │ └── items/
│ │ └── default.php
│ ├── language/
│ │ └── en-GB/
│ │ ├── com_mycomponent.ini
│ │ └── com_mycomponent.sys.ini
│ ├── access.xml # ACL definitions
│ └── config.xml # Component options
├── site/
│ ├── src/
│ │ ├── Controller/
│ │ ├── Model/
│ │ ├── View/
│ │ ├── Dispatcher/
│ │ ├── Helper/
│ │ └── Service/
│ │ └── Router.php # SEF URL routing
│ ├── forms/
│ ├── tmpl/
│ └── layouts/
└── media/
├── joomla.asset.json # Web asset definitions
├── css/
├── js/
└── images/
5. MVC Request Flow
Understanding the request lifecycle helps you know what to create:
URL → Router → Dispatcher → Controller → Model → Table (DB) → View → Template → Response
- Router parses the URL to determine component, view, and ID
- Dispatcher (admin/src/Dispatcher/) checks access and creates the controller
- Controller handles the action (display, save, delete, publish)
- Model fetches or manipulates data using database queries
- Table maps to a single database row (load, store, delete, check)
- View (HtmlView.php) prepares data for rendering
- Template (tmpl/) outputs the HTML
Admin URL Routing (task= vs view=)
Admin URLs use two patterns: task={controller}.{method} triggers a controller action (and checks out the record), while view={view} just displays a view. Linking from list views to edit forms MUST use task= — using view=item&layout=edit skips checkout and lets two users overwrite each other. For the full pattern table (when to use which), the checked-out handling pattern in list templates, and the JOIN that surfaces the editor name, read references/admin-routing.md.
6. Naming Conventions
Joomla has strict naming that connects everything automatically:
| What | Naming Pattern | Example |
|---|
| Single-item controller | {Entity}Controller | BookingController |
| List controller | {Entity}sController (plural) | BookingsController |
| Single-item model | {Entity}Model | BookingModel |
| List model | {Entity}sModel (plural) | BookingsModel |
| Table class | {Entity}Table | BookingTable |
| Single-item view dir | View/{Entity}/HtmlView.php | View/Booking/HtmlView.php |
| List view dir | View/{Entity}s/HtmlView.php | View/Bookings/HtmlView.php |
| Edit template | tmpl/{entity}/edit.php | tmpl/booking/edit.php |
| List template | tmpl/{entity}s/default.php | tmpl/bookings/default.php |
Some projects use a vendor prefix on entity names (e.g., AcmeBooking instead of plain Booking). This is optional but helps avoid naming collisions with other extensions.
The view name in the URL (e.g., &view=bookings) must match the View directory name (case-insensitive on the URL side, but the directory must match the class namespace).
7. Data, distribution, and storage — pointer summary
The data + distribution layers that every extension type touches are documented in their own references so this skill stays focused on the architecture above. Read the one(s) you need:
- Database schema — install SQL (
admin/sql/install.mysql.utf8.sql), update SQL files in admin/sql/updates/mysql/, the #__ prefix Joomla replaces at runtime, and the DDL-vs-DML rule (SQL files do DDL only; data migrations go in the install script). references/database.md.
- Language files — INI file conventions, key prefixes per extension type (
COM_, MOD_, PLG_<GROUP>_<ELEMENT>_, LIB_), plurals, and Text::script() JS registration. references/language-files.md.
- Web Asset Manager —
joomla.asset.json schema, $wa->useStyle() / $wa->useScript(), dependencies, attributes. references/web-assets.md.
- Manifest XML —
<extension> root attributes, <files>, <media>, <languages>, <scriptfile>, the <administration> block with <menu> / <submenu>, and the <install> / <update> blocks. Universal elements in references/manifest.md; the full component-flavored manifest template in references/component.md.
- Install / update scripts —
preflight / install / update / postflight / uninstall hooks and DML migrations. references/install-script.md.
- Changelog XML — what powers the in-admin "View changelog" link; categories (
<security>, <fix>, etc.), required nodes, element/type values per extension type. references/packaging.md § Changelog XML.
- Update server — update XML,
<targetplatform> regex, SHA hashes, per-extension-type tweaks (folder / client attributes). references/update-server.md.
Workflow: Adding a New Entity to a Component
When adding a new entity (e.g., "Location" to a component), create these files:
- Table —
admin/src/Table/LocationTable.php
- Model (single) —
admin/src/Model/LocationModel.php (extends FormModel)
- Model (list) —
admin/src/Model/LocationsModel.php (extends ListModel)
- Controller (single) —
admin/src/Controller/LocationController.php (extends FormController)
- Controller (list) —
admin/src/Controller/LocationsController.php (extends AdminController)
- View (edit) —
admin/src/View/Location/HtmlView.php
- View (list) —
admin/src/View/Locations/HtmlView.php
- Templates —
admin/tmpl/location/edit.php and admin/tmpl/locations/default.php
- Form XML —
admin/forms/location.xml
- SQL — Add CREATE TABLE to install SQL, add update SQL file
- Language strings — Add to
.ini files
- Menu entry — Add
<menu> to manifest XML submenu
For detailed code templates of each file, read references/component.md.
Component Lifecycle & Patterns
The internal mechanics of how components work — the model save flow (Controller::save() → Model::prepareTable() → Table::bind() → Table::check() → Table::store()), the getItem() / save() model overrides, Table::bind() JSON-encoding params, filter form XML for searchtools, the component install/update script class, config.xml component options, site-side differences (caching, access filtering, menu params, page metadata), the Extension class implementing CategoryServiceInterface / TagServiceInterface, custom controller tasks and AJAX, registering HTMLHelper services, and form XML conditional fields (showon) plus fieldset-to-tab rendering — all live in references/component-lifecycle.md.
Advanced Component Features
Component-level features beyond core MVC: the toolbar API (getDocument()->getToolbar(), dropdown groups, save dropdowns), batch processing (AdminModel batch methods, dialog template, controller batch() override), drag-drop ordering (the ordering column, js-draggable markup, AJAX save), tags integration (TagServiceInterface, TaggableTableInterface), content versioning (VersionableModelInterface, #__content_types registration, save_history config), workflow integration (WorkflowServiceInterface), the webservices API plugin (onBeforeApiRoute, createCRUDRoutes, JSON:API view), mail templates (MailTemplate, #__mail_templates), dashboard views (manifest <dashboards>, presets, cpanel-* positions), and custom form validation rules (FormRule, addruleprefix). For full code examples and the toolbar method list, read references/component-advanced.md.
Editor API
Joomla's Editor API provides a unified interface for WYSIWYG editors (TinyMCE, CodeMirror, None) and editor extension buttons. Coverage: the modern JoomlaEditor JS API (get, getActive, getValue, setValue, getSelection, replaceSelection, disable, getRawInstance, getType); the legacy Joomla.editors.instances proxy; the JoomlaEditorDecorator pattern for implementing a custom editor; XTD button plugins (onEditorButtonsSetup, Button class, JoomlaEditorButton.registerAction); the modal-button content-selection flow with postMessage and joomla:content-select; the editor form field type with all attributes (buttons, hide, height, width, editor, filter, asset_field, created_by_field, syntax); and editor plugin registration via onEditorSetup with AbstractEditorProvider.
For full code examples and the button-action behavior table, read references/editor-api.md.
Layouts (LayoutHelper)
Joomla's layout system provides reusable, overridable PHP template fragments via LayoutHelper::render(). Layout IDs are dot-separated paths (joomla.form.field.text → layouts/joomla/form/field/text.php). Templates can override any layout by copying it to templates/{template}/html/layouts/. For the rendering API, layout-override priority table, sublayout pattern, and the list of key built-in layouts (searchtools, edit.title_alias, batch.*, content.info_block, form.renderfield, pagination.default, etc.), read references/layouts.md.
Form Fields
Joomla ships ~90 built-in form field types and a clean extension model for custom field classes. Coverage: built-in field reference (basic inputs, selection fields, file/media fields, special fields including subform, rules, ordering, note, componentlayout), the subform repeatable-group pattern with external/inline form sources, the media picker (types, preview, directory attributes), authoring custom fields by extending ListField / GroupedlistField / FormField / TextField / PredefinedlistField, the addfieldprefix form attribute, and modern layout-based field rendering with getLayoutData().
For the full type reference, custom-field examples, and layout overrides, read references/form-fields.md.
Menu Item Types (Site Views)
Menu item types define what appears in Joomla's Menu Manager. Joomla scans components/{component}/tmpl/{view}/ for XML files: each .xml matching a layout name (default.xml, blog.xml, etc.) becomes a selectable menu item type. For the full XML structure (list and single-item variants), <layout> / <fields name="request"> / <fields name="params"> semantics, the useglobal="true" cascade rule, and the multi-layout-per-view convention, read references/menu-items.md.
Libraries and Composer
Libraries are shared PHP packages installed under libraries/ that any extension can consume; Composer is the package manager for any third-party PHP libs you bundle into your extension. The full reference — when to use a library vs. a helper, the composer.json template, the vendor-dir: libraries/vendor convention, runtime autoload-loading from provider.php, the common Joomla-ecosystem libraries to use vs. avoid duplicating, and npm front-end build tooling — lives in references/library.md.
Extension Packaging (Building the Installable ZIP)
Joomla extensions ship as ZIP files installed through the admin installer (System → Install → Extensions). For manual packaging, the build-script pattern, package manifests (multiple extensions bundled), and the include/exclude checklist for what belongs in the ZIP, read references/packaging.md.
Real-World Reference: Production Components
When working on a Joomla project, check whether the project has its own CLAUDE.md or CONTRIBUTING.md at the repo root — these often define project-specific naming conventions, build commands, and coding standards that should take precedence over this skill's generic patterns.
Common patterns seen in production Joomla 5+ components:
- Entity prefixes — Some projects prefix entity names to avoid collisions (e.g., a
<Vendor> prefix on every Model/View/Table class). Follow the project's existing convention.
- Plugin groups — Real components typically ship with several plugin types: content, finder (Smart Search), schemaorg (structured data), system, task (scheduled jobs), and webservices (REST API).
- Module variants — Both admin-side and site-side modules in
modules/admin/ and modules/site/.
- Build tooling — Composer for PHP dependencies + npm for JS/CSS asset compilation. Look for
composer.json and package.json at the project root.
- Testing — PHPUnit for PHP (unit + integration tests in
tests/), Jest for JavaScript. See the Testing section below for patterns.
- Non-standard vendor paths — Some projects place Composer's
vendor/ directory in a custom location (e.g., libraries/vendor/). Check composer.json for the vendor-dir config.
- Media asset pipeline — Source JS/CSS in a build directory (e.g.,
build/media_source/) compiled to media/ via npm scripts. The media/ directory may be gitignored.
Testing
PHPUnit (PHP) and Jest (JavaScript) patterns for Joomla 5+ extensions, anchored on the principle Joomla core uses for its own tests: load real CMS classes, don't stub them. Topics covered: tests/Unit/ + tests/Integration/ directory layout, phpunit.xml configuration, the bootstrap that loads Joomla's vendor autoloader plus your component's PSR-4 autoloader, the getQueryStub() helper for a real DatabaseQuery with only 2 abstract methods stubbed, model/table/helper test patterns, Jest with jsdom for JavaScript with mocked Joomla globals, and four high-stakes testing gotchas (DatabaseInterface lacks createQuery(), CMSApplicationInterface lacks getSession(), the bootstrap-must-load-libraries/vendor/autoload.php rule, createMock() vs createStub() for expectations).
For full setup, code patterns, and gotcha details, read references/testing.md.
Version Migration: Joomla 5 → 6 (and Beyond)
When migrating an extension from Joomla 5 to Joomla 6 (or writing code that supports both), apply these changes systematically:
Step 1: Query Builder
Find all instances of $db->getQuery(true) and replace with $db->createQuery(). Both work on Joomla 5, but only createQuery() is guaranteed going forward.
Step 2: Input Classes
Replace any use Joomla\CMS\Input\Input with use Joomla\Input\Input. The CMS wrapper is removed in Joomla 6. Note: $this->input in controllers already works correctly on both versions.
Step 3: CMSObject → stdClass
If code uses $item->get('property') or $item->set('property', $value) on objects returned by getItem(), replace with direct property access: $item->property and $item->property = $value. In Joomla 6, getItem() returns stdClass, not CMSObject.
Step 4: Filesystem Classes
Replace use Joomla\CMS\Filesystem\File / Folder / Path with PHP native functions (file_put_contents, mkdir, is_dir, etc.) or Symfony Filesystem. The CMS Filesystem classes move behind the compat plugin in J6 and are removed in J7.
Step 5: Factory Methods
Search for and replace:
Factory::getUser() → $this->getCurrentUser() (in models) or $this->getIdentity() (in controllers/views)
Factory::getApplication() → Inject via constructor or use $this->getApplication()
Factory::getDbo() → $this->getDatabase() or inject DatabaseInterface
Step 6: Error Handling
Replace $model->getError() / $model->setError() patterns with try/catch and thrown exceptions (\RuntimeException, \InvalidArgumentException).
Step 7: Test
After migration, test with the "Behaviour - Backward Compatibility 6" plugin disabled to confirm the extension runs natively on Joomla 6 without compatibility shims.
Future Versions
This skill targets Joomla 6 native patterns that also work on Joomla 5. As Joomla 7+ introduces additional changes, this migration checklist will expand. The core principle remains: use the modern API from the framework layer (Joomla\Database, Joomla\Input) rather than the CMS convenience wrappers.
Common Gotchas & Pitfalls
Hard-won lessons from real Joomla 5/6 extension development — easy to get wrong because IDE autocompletion, documentation gaps, or reasonable assumptions lead you astray. Topics covered: BaseController vs FormController, J5 controller API differences ($this->input not getInput()), event dispatching for J5 compatibility, plugin manifest naming, plugin language file conventions, task plugin language keys, AdminModel/Table save workflow, task= routing, form.validate asset, Table::check(), HTTP client class, Registry::get() defaults, Text::script() registration, Joomla.Text._() truthy-key trap, batch routing, the 3-part SEF router contract (router class + RouterServiceInterface + RouterFactory), hidden menu items for SEF, router callback naming, WAM URI auto-resolution / non-standard paths / inline assets, Bootstrap 5.3 dark mode classes, dynamic modal cleanup, and getStoreId() in ListModel.
For full details and code examples, read references/gotchas.md.