| name | joomla6-general-concepts |
| description | Core Joomla 6 (6.0-era) general concepts skill. Covers core architecture, dependency injection, ACL, database operations with prepared statements, forms and custom fields, SEF routing, Web Asset Manager, Table class, and multilingual support. Targets native Joomla 6 patterns only — no Joomla 3/4 J* classes, no FOF, no jQuery. |
Joomla 6 General Concepts Skill
Comprehensive reference for Joomla 6 core architecture: dependency injection, database operations, forms, routing, Web Asset Manager, ACL, Table class, and multilingual support.
When to Use This Skill
Trigger this skill when:
- Working with Joomla's Dependency Injection container (
Joomla\DI\Container)
- Performing database operations (SELECT / INSERT / UPDATE / DELETE) via the query builder
- Creating forms and custom form fields, validators, or rules
- Implementing SEF routing in components (
RouterView, RouterViewConfiguration)
- Managing CSS/JS via the Web Asset Manager and
joomla.asset.json
- Working with ACL — actions, rules, asset records, view levels
- Understanding Joomla's request lifecycle, Application, Document, and Extension classes
- Implementing multilingual support, language strings, and
Text::script()
- Building or updating Joomla 6 components, modules, plugins, templates, or libraries
- Migrating extensions from Joomla 3/4/5 (or FOF) to native Joomla 6
Hard Rules
Every piece of code produced under this skill must:
- Use PHP 8.3/8.4 with
declare(strict_types=1); at the top of every PHP file
- Use PSR-4 namespacing matching the on-disk layout
- Use
\defined('_JEXEC') or die; (with leading backslash inside namespaces)
- Use dependency injection — never service location anti-patterns
- Use prepared statements with
bind() and ParameterType for every query
- Use
quoteName() for all identifiers, #__ for the table prefix
- Use
WebAssetManager for all CSS/JS — never raw <script>/<link> tags
- Use vanilla JavaScript (ES2020+) — never jQuery
- Use Bootstrap 5 classes and utilities — never Bootstrap 2/3/4
Every piece of code must never:
- Use any
J*-prefixed Joomla 3 class (JFactory, JTable, JModel, JController, JHtml, JText, JRoute, JUri, JRequest, JInput, JLog, JFolder, JFile, JLoader::register, etc.)
- Use FOF code (
F0FController, F0FModel, F0FTable, F0FViewHtml, F0FPlatform)
- Use jQuery in any form — including as a dependency in
joomla.asset.json
- Use
Factory::getDbo() — must use DI / $this->getDatabase() / DatabaseInterface
- Use
Factory::getUser() / Factory::getUser($id) — removed in Joomla 6
- Use
Joomla\CMS\Filesystem\* — moved to Joomla\Filesystem\* in Joomla 6
- Modify any Joomla core file:
/libraries/, /api/, /includes/, /cli/, /installation/, core components (com_content, com_users, com_config, com_modules, com_plugins, com_templates, com_categories, com_menus, com_media, com_installer, com_admin, com_login, com_cpanel), core plugins, core modules, or core templates (cassiopeia, atum)
Extend Joomla via plugins, event subscribers, service overrides, template overrides (in your own template), and your own component/module/plugin/library.
Quick Reference
Getting a Database Instance (Joomla 6)
use Joomla\CMS\Factory;
use Joomla\Database\DatabaseInterface;
$db = $this->getDatabase();
$db = Factory::getContainer()->get(DatabaseInterface::class);
$query = $db->getQuery(true);
Do NOT use Factory::getDbo() — it is legacy and must not appear in new code.
Database SELECT (prepared statements)
use Joomla\CMS\Factory;
use Joomla\Database\DatabaseInterface;
use Joomla\Database\ParameterType;
$db = Factory::getContainer()->get(DatabaseInterface::class);
$query = $db->getQuery(true);
$query->select($db->quoteName(['user_id', 'profile_key', 'profile_value']))
->from($db->quoteName('#__user_profiles'))
->where($db->quoteName('profile_key') . ' LIKE :profile_key')
->order($db->quoteName('ordering') . ' ASC')
->bind(':profile_key', 'custom.%', ParameterType::STRING);
$db->setQuery($query);
$results = $db->loadObjectList();
Database INSERT
use Joomla\Database\ParameterType;
$userId = 1001;
$profileKey = 'custom.message';
$profileValue = 'My custom value';
$ordering = 1;
$query = $db->getQuery(true);
$query->insert($db->quoteName('#__user_profiles'))
->columns($db->quoteName(['user_id', 'profile_key', 'profile_value', 'ordering']))
->values(':user_id, :profile_key, :profile_value, :ordering')
->bind(':user_id', $userId, ParameterType::INTEGER)
->bind(':profile_key', $profileKey, ParameterType::STRING)
->bind(':profile_value', $profileValue, ParameterType::STRING)
->bind(':ordering', $ordering, ParameterType::INTEGER);
$db->setQuery($query);
$db->execute();
$profile = new \stdClass();
$profile->user_id = 1001;
$profile->profile_key = 'custom.message';
$profile->profile_value = 'My value';
$profile->ordering = 1;
$db->insertObject('#__user_profiles', $profile);
Database UPDATE
use Joomla\Database\ParameterType;
$query = $db->getQuery(true);
$fields = [
$db->quoteName('profile_value') . ' = :profile_value',
$db->quoteName('ordering') . ' = :ordering',
];
$conditions = [
$db->quoteName('user_id') . ' = :user_id',
$db->quoteName('profile_key') . ' = :profile_key',
];
$query->update($db->quoteName('#__user_profiles'))
->set($fields)
->where($conditions)
->bind(':profile_value', 'Updated message', ParameterType::STRING)
->bind(':ordering', 2, ParameterType::INTEGER)
->bind(':user_id', 42, ParameterType::INTEGER)
->bind(':profile_key', 'custom.message', ParameterType::STRING);
$db->setQuery($query);
$db->execute();
Database DELETE
use Joomla\Database\ParameterType;
$query = $db->getQuery(true);
$query->delete($db->quoteName('#__user_profiles'))
->where($db->quoteName('user_id') . ' = :user_id')
->where($db->quoteName('profile_key') . ' LIKE :profile_key')
->bind(':user_id', 1001, ParameterType::INTEGER)
->bind(':profile_key', 'custom.%', ParameterType::STRING);
$db->setQuery($query);
$db->execute();
Component Service Provider (services/provider.php)
<?php
declare(strict_types=1);
\defined('_JEXEC') or die;
use Joomla\CMS\Component\Router\RouterFactoryInterface;
use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
use Joomla\CMS\Extension\ComponentInterface;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory;
use Joomla\CMS\Extension\Service\Provider\MVCFactory;
use Joomla\CMS\Extension\Service\Provider\RouterFactory;
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 ComponentDispatcherFactory('\\Vendor\\Component\\Example'));
$container->registerServiceProvider(new MVCFactory('\\Vendor\\Component\\Example'));
$container->registerServiceProvider(new RouterFactory('\\Vendor\\Component\\Example'));
$container->set(
ComponentInterface::class,
function (Container $container) {
$component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
$component->setMVCFactory($container->get(MVCFactoryInterface::class));
$component->setRouterFactory($container->get(RouterFactoryInterface::class));
return $component;
}
);
}
};
Module Service Provider
<?php
declare(strict_types=1);
\defined('_JEXEC') or die;
use Joomla\CMS\Extension\Service\Provider\HelperFactory;
use Joomla\CMS\Extension\Service\Provider\Module;
use Joomla\CMS\Extension\Service\Provider\ModuleDispatcherFactory;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
return new class () implements ServiceProviderInterface {
public function register(Container $container): void
{
$container->registerServiceProvider(new ModuleDispatcherFactory('\\Vendor\\Module\\Example'));
$container->registerServiceProvider(new HelperFactory('\\Vendor\\Module\\Example\\Site\\Helper'));
$container->registerServiceProvider(new Module());
}
};
Plugin Service Provider
<?php
declare(strict_types=1);
\defined('_JEXEC') or die;
use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\Event\DispatcherInterface;
use Vendor\Plugin\Content\Example\Extension\Example;
return new class () implements ServiceProviderInterface {
public function register(Container $container): void
{
$container->set(
PluginInterface::class,
function (Container $container) {
$plugin = new Example(
$container->get(DispatcherInterface::class),
(array) PluginHelper::getPlugin('content', 'example')
);
$plugin->setApplication(Factory::getApplication());
return $plugin;
}
);
}
};
Boot a Component or Module
use Joomla\CMS\Factory;
$app = Factory::getApplication();
$component = $app->bootComponent('com_example');
$module = $app->bootModule('mod_example', 'site');
Web Asset Manager — Registering CSS/JS
Step 1 — Create joomla.asset.json (extension root, or per-client folder):
{
"$schema": "https://developer.joomla.org/schemas/json-schema/web_assets.json",
"name": "com_example",
"version": "1.0.0",
"assets": [
{
"name": "com_example.admin",
"type": "script",
"uri": "com_example/admin.js",
"attributes": { "defer": true, "type": "module" },
"dependencies": ["core"],
"version": "1.0.0"
},
{
"name": "com_example.admin",
"type": "style",
"uri": "com_example/admin.css",
"version": "1.0.0"
}
]
}
Do not declare jquery as a dependency. Joomla 6 code is vanilla ES2020+.
Step 2 — Use the asset in PHP:
use Joomla\CMS\Factory;
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->useStyle('com_example.admin')
->useScript('com_example.admin');
$wa->getRegistry()->addExtensionRegistryFile('mod_example');
$wa->useScript('mod_example.frontend');
Inline Script and Passing PHP Variables to JS
use Joomla\CMS\Factory;
$document = Factory::getApplication()->getDocument();
$wa = $document->getWebAssetManager();
$wa->addInlineScript("document.addEventListener('DOMContentLoaded', () => { /* ... */ });");
$document->addScriptOptions('com_example.config', [
'apiBase' => '/api/index.php/v1/example',
'pageSize' => 25,
]);
const config = Joomla.getOptions('com_example.config');
console.log(config.pageSize);
Pass Language Strings to JavaScript
use Joomla\CMS\Language\Text;
Text::script('COM_EXAMPLE_ERROR_MESSAGE');
const message = Joomla.Text._('COM_EXAMPLE_ERROR_MESSAGE');
Joomla.renderMessages({ error: [message] });
SEF Routing — Building URLs
use Joomla\CMS\Router\Route;
$url = Route::_('index.php?option=com_content&view=article&id=21&catid=50&lang=en-GB');
Component Router with RouterView
<?php
declare(strict_types=1);
namespace Vendor\Component\Example\Site\Service;
\defined('_JEXEC') or die;
use Joomla\CMS\Application\SiteApplication;
use Joomla\CMS\Component\Router\RouterView;
use Joomla\CMS\Component\Router\RouterViewConfiguration;
use Joomla\CMS\Component\Router\Rules\MenuRules;
use Joomla\CMS\Component\Router\Rules\NomenuRules;
use Joomla\CMS\Component\Router\Rules\StandardRules;
use Joomla\CMS\Menu\AbstractMenu;
final class Router extends RouterView
{
public function __construct(SiteApplication $app, AbstractMenu $menu)
{
$this->registerView(new RouterViewConfiguration('featured'));
$category = (new RouterViewConfiguration('category'))
->setKey('id')
->setNestable();
$this->registerView($category);
$article = (new RouterViewConfiguration('article'))
->setKey('id')
->setParent($category, 'catid');
$this->registerView($article);
parent::__construct($app, $menu);
$this->attachRule(new MenuRules($this));
$this->attachRule(new StandardRules($this));
$this->attachRule(new NomenuRules($this));
}
}
Form Field Definitions (XML)
All <field> elements use one attribute per line with /> on its own line. Boolean yes/no fields use the switcher layout.
<field
name="title"
type="text"
label="COM_EXAMPLE_FIELD_TITLE_LABEL"
description="COM_EXAMPLE_FIELD_TITLE_DESC"
required="true"
maxlength="255"
filter="string"
/>
<field
name="catid"
type="category"
extension="com_example"
label="JCATEGORY"
required="true"
/>
<field
name="state"
type="list"
label="JSTATUS"
default="1"
>
<option value="1">JPUBLISHED</option>
<option value="0">JUNPUBLISHED</option>
<option value="-2">JTRASHED</option>
</field>
<field
name="featured"
type="radio"
label="JFEATURED"
default="0"
layout="joomla.form.field.radio.switcher"
filter="integer"
>
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
Custom Form Field with Namespace Prefixes
<?xml version="1.0" encoding="utf-8"?>
<form
addruleprefix="Vendor\Component\Example\Administrator\Rule"
addfieldprefix="Vendor\Component\Example\Administrator\Field"
>
<fieldset name="basic">
<field
name="custom"
type="myfield"
label="COM_EXAMPLE_FIELD_CUSTOM_LABEL"
/>
</fieldset>
</form>
addfieldprefix is required on the <form> (or per <fieldset>/<field>) for plugin/module forms and any subform sources to resolve custom field classes.
Table Class — Basic Operations
$table = $this->getTable('Example');
$table->load($id);
echo $this->escape($table->title);
$data = ['title' => 'New Title', 'alias' => 'new-title'];
$table->bind($data);
$table->check();
$table->store();
$table->save($data);
$table->delete($id);
ACL — Asset Records on Table Subclasses
protected function _getAssetName(): string
{
return 'com_example.item.' . (int) $this->id;
}
protected function _getAssetTitle(): string
{
return $this->title;
}
protected function _getAssetParentId(?Table $table = null, $id = null): int
{
$asset = self::getInstance('Asset');
$asset->loadByName('com_example');
return (int) $asset->id;
}
Current User (CRITICAL — Factory::getUser() removed in Joomla 6)
$user = Factory::getUser();
$user = Factory::getUser($userId);
$user = $this->getCurrentUser();
$user = Factory::getApplication()->getIdentity();
use Joomla\CMS\User\UserFactoryInterface;
$user = Factory::getContainer()
->get(UserFactoryInterface::class)
->loadUserById((int) $userId);
Filesystem (moved out of Joomla\CMS in Joomla 6)
use Joomla\CMS\Filesystem\File;
use Joomla\CMS\Filesystem\Folder;
use Joomla\CMS\Filesystem\Path;
use Joomla\Filesystem\File;
use Joomla\Filesystem\Folder;
use Joomla\Filesystem\Path;
Namespace Definitions in Manifests
<namespace path="src">Vendor\Component\Example</namespace>
<namespace path="src">Vendor\Module\Example</namespace>
<namespace path="src">Vendor\Plugin\Content\Example</namespace>
Reading Configuration
use Joomla\CMS\Factory;
$config = Factory::getApplication()->getConfig();
$dbHost = $config->get('host');
$config = Factory::getContainer()->get('config');
Common Patterns
| Pattern | Use Case |
|---|
Factory::getContainer()->get(DatabaseInterface::class) | Get database instance outside a model |
$this->getDatabase() | Get database instance inside a model |
$app->bootComponent('com_example') | Boot a component extension |
$app->bootModule('mod_example', 'site') | Boot a module extension |
$wa->useStyle('com_example.admin') | Include a CSS asset |
$wa->useScript('com_example.admin') | Include a JS asset |
Route::_('index.php?...') | Build an SEF URL |
$table->load($id) / $table->save($data) | Load/save a single record |
Text::script('LANG_KEY') | Pass a language string to JS |
Factory::getApplication()->getIdentity() | Get the current user |
Reference Files
This skill includes detailed documentation in references/:
architecture.md — DI, namespaces, Extension/Dispatcher classes
database.md — Database operations, Table class, nested sets
forms.md — Form fields, validation, custom fields
acl.md — Access control, permissions, view levels
routing.md — SEF URLs, parse/build, RouterView
content.md — Categories, menus, multilingual
assets.md — Web Asset Manager, JavaScript integration
api.md — Web services / REST API
utilities.md — Logging, mail, icons, workflows
Use view to read specific reference files when detailed information is needed.
Best Practices
- Use prepared statements — always
bind() with ParameterType::*. Never concatenate values into SQL.
- Use dependency injection — fetch services from the DI container, not via static
getInstance().
- Use the Web Asset Manager — declare assets in
joomla.asset.json and useStyle/useScript them. Never inject raw <script> / <link> tags.
- Follow PSR-4 — namespace must mirror the path under
src/.
- Implement
services/provider.php for every component, module, and plugin — register MVCFactory, ComponentDispatcherFactory, RouterFactory as appropriate.
- Use the Table class for single-record CRUD; use Models for list and form logic.
- Always escape output in views with
$this->escape().
- Always check the CSRF token on state-changing requests with
$this->checkToken() / Session::checkToken().
- Mark classes
final when they are not designed for extension.
- Never modify Joomla core files — extend via plugins, events, services, and overrides.
Related Skills
joomla6-extensions — extension structure, MVC, manifests, service providers
joomla6-security — XSS, SQLi, CSRF, input handling
joomla6-code-reviewer — audits code against these patterns
joomla6-component-architect — component architecture and migration blueprints
Resources