Components in Joomla
A Joomla page is built from many small parts working together, but only one part produces the main content. That part is the component.
Almost everything you manage in the Joomla backend is a component: articles, contacts, users, menus, the Media Manager, and the global configuration. Once you understand how one component is built, you can read, override, and extend almost every other component, whether it ships with Joomla core or comes from a third-party developer.
From "what builds the body of the page" to MVC, the service container, routing, and writing your own.
This article explains how Joomla components really work. It covers the basics for website owners and editors, the practical structure for administrators, and the technical details for developers. You will learn what a component is, how it is built around the MVC pattern, how it is wired into Joomla through a service container, how it stores data and permissions, and how to extend it the right way.
The goal is simple: help you understand Joomla components well enough to work with them confidently.
1. The Basics
1.1 What is a Component?
A component is the main application that builds the body of a Joomla page. Think of it as a mini-application running inside Joomla. The CMS is the operating system; components are the apps that run on it.
The most important rule to remember is this:
A Joomla page is assembled from many extensions, but exactly one component produces its main content.
1.2 The Five Extension Types
A component is one member of a family of five extension types. Knowing the others is what makes the word "component" meaningful:
| Type | Role | Per page | Prefix |
|---|---|---|---|
| Component | Main page content / application | exactly 1 | com_ |
| Module | Small boxes around the content | many | mod_ |
| Plugin | Event-driven behaviour in the background | many | plg_ |
| Template | Look, feel and page layout | 1 site + 1 admin | tpl_ |
| Language | Translations | 1 active | - |
Components, modules, and plugins are often shipped together in one installable package (prefix pkg_).
1.3 The Page as a Newspaper
Here is the mental model. The template is the page frame. Inside it, the component fills the main area and the modules sit around it:
┌────────────────────────────────┐
│ TEMPLATE (the page frame) │
│ ┌──────┐ ┌─────────┐ ┌──────┐ │
│ │MODULE│ │COMPONENT│ │MODULE│ │
│ │(menu)│ │ the main│ │(login│ │
│ │ │ │ content │ │ ) │ │
│ └──────┘ └─────────┘ └──────┘ │
│ ┌─────────────────────┐ │
│ │MODULE (footer) │ │
│ └─────────────────────┘ │
└────────────────────────────────┘
If the page were a newspaper, the component is the main article. The modules are the sidebars, ads, and footers around it.
1.4 The com_ Naming Convention
Every component name starts with com_. The option parameter in the URL tells Joomla which component to run:
index.php?option=com_content&view=article&id=42
│ │ │
│ │ └─ which item
│ └─ which screen (the "view")
└─ which component
This single line is the key to understanding all Joomla routing and SEF (search engine friendly) URLs.
1.5 Components You Already Use Every Day
Open the Administrator and look at the menu. Nearly every item is a component:
| Component | Purpose |
|---|---|
com_content |
Articles and article categories (the core CMS) |
com_contact |
Contacts and contact forms |
com_banners |
Self-hosted banner ad manager |
com_newsfeeds |
RSS feed aggregation |
com_media |
The Media Manager |
com_users |
Accounts, groups, and access levels |
com_menus |
Menu and menu-item management |
com_modules |
Module management |
com_categories |
Shared category management |
com_fields |
Shared custom fields |
com_config |
Global configuration |
com_installer |
Install and update extensions |
Many of these are infrastructure. For example, com_categories and com_fields exist mainly so that other components can reuse them.
2. Site versus Administrator
2.1 A Component Lives in Two Worlds
A component has two sides: the front-end that visitors see, and the back-end where you manage it. They live in two different folders:
public_html/
├── components/ ← SITE (front-end, what visitors see)
│ └── com_content/
└── administrator/
└── components/ ← ADMIN (back-end, where you manage it)
└── com_content/
- The Site part renders the article on the public page.
- The Administrator part is the article editor, the list views, and the batch tools.
A component may have both sides, or only one:
| Component | Site | Admin |
|---|---|---|
com_content |
Yes | Yes |
com_installer |
No | Yes (admin-only) |
com_ajax |
Yes | Yes (no UI - a service endpoint) |
2.2 The Request Lifecycle
It helps to know what happens on every page load. The component owns the middle of this chain:
Browser request
│ index.php?option=com_content&view=article&id=42
▼
CMS Application (bootstrap, session, plugins onAfterInitialise)
▼
Router → SEF URL resolved into option / view / id
▼
Dispatcher → loads the component (com_content)
▼
Controller → picks the task
▼
Model → fetches article 42 from the database
▼
View + Layout → renders the HTML
▼
Template wraps it, modules render around it → page sent to browser
Notice that the component is responsible for the steps from Dispatcher to View. Everything before it is the CMS doing the routing; everything after it is the template doing the framing.
Back to top3. Inside a Component: The MVC Structure
3.1 The Modern Folder Layout
Since Joomla 4, components follow a clear, namespaced folder structure. Here is the admin side of com_content in Joomla 4, 5, and 6:
administrator/components/com_content/
├── content.xml ← manifest (name, version, files, install)
├── services/
│ └── provider.php ← DI service registration (the entry point)
├── src/ ← PSR-4 namespaced PHP classes
│ ├── Controller/ ← decides WHAT to do
│ ├── Model/ ← talks to the DATABASE
│ ├── View/ ← prepares data for output
│ ├── Table/ ← maps one DB row to an object
│ ├── Field/ ← custom form fields
│ ├── Service/ ← routing, HTML helpers, category factory
│ ├── Helper/ ← shared utility code
│ └── Extension/ ← the component class itself
├── tmpl/ ← the actual HTML layouts (.php)
├── forms/ ← XML form definitions
├── sql/ ← install / update / uninstall SQL
└── language/ ← translation .ini files
An important change to remember: since Joomla 4 there is no controller.php entry file. The entry point is now services/provider.php.
3.2 The Three MVC Roles
MVC stands for Model-View-Controller. It is a pattern that splits the work into three clear roles.
Controller - the traffic cop
- Reads the request (for example
task=article.save). - Calls the right model, then hands off to the right view.
- Contains no HTML and no SQL.
Model - the brain
- Holds the business logic and the database access.
- Answers requests like "give me article 42", "save this", "publish these IDs", or "build this list query".
View and Layout - the face
- The View object gathers data from the Model.
- The Layout (a file in
tmpl/) outputs the actual HTML.
This separation of concerns is what makes a component easier to maintain, override, and test.
3.3 The Table Class
Between the Model and the database sits the Table. A Table object maps to a single database row. This is known as the Active Record pattern:
$table = $this->getTable(); // e.g. ContentTable
$table->load($id); // SELECT * FROM #__content WHERE id = ?
$table->title = 'New title';
$table->store(); // INSERT / UPDATE
$table->delete($id);
Tables also drive publishing state, ordering, check-in and check-out, and the JSON params column that many items use to store their settings.
4. The Manifest and the Database
4.1 The Install Manifest
Every component declares itself to Joomla in an XML manifest file. For com_content this file is content.xml:
<extension type="component" method="upgrade">
<name>com_content</name>
<version>6.0.0</version>
<namespace path="src">Joomla\Component\Content</namespace>
<files folder="site">...</files> <!-- front-end files -->
<administration>
<files folder="admin">...</files> <!-- back-end files -->
<menu>com_content</menu> <!-- admin menu entry -->
<submenu>...</submenu>
</administration>
<install><sql><file driver="mysql">sql/install.mysql.utf8.sql</file></sql></install>
<update><schemas><schemapath type="mysql">sql/updates/mysql</schemapath></schemas></update>
<uninstall><sql>...</sql></uninstall>
</extension>
This one file drives install, update, and uninstall. It tells Joomla which files to copy, which SQL to run, and which PSR-4 namespace to register.
4.2 Where Components Are Recorded
Every installed extension, components included, has one row in the #__extensions table:
| Column | For a component |
|---|---|
type |
component |
element |
com_content |
name |
com_content |
enabled |
1 or 0 |
params |
JSON - the component's Options (permissions, defaults, and so on) |
The Options button in the top-right of any component edits the params JSON in this row. There is no separate config table.
4.3 Each Component Owns Its Tables
A component typically creates its own data tables. They are prefixed with #__, which is a placeholder for the site's table prefix:
| Component | Core tables |
|---|---|
com_content |
#__content, #__content_frontpage, #__content_types |
com_contact |
#__contact_details |
com_banners |
#__banners, #__banner_clients, #__banner_tracks |
com_newsfeeds |
#__newsfeeds |
com_categories |
#__categories (shared by many components) |
com_fields |
#__fields, #__fields_values, #__fields_groups |
The #__ placeholder is replaced at runtime with the real prefix from configuration.php (for example jos_).
4.4 ACL and the Assets Table
Components do not store their own permissions. They plug into Joomla's central Access Control List (ACL), which is backed by one shared table: #__assets.
#__assets (nested set, like categories)
├── root.1 ← global config
│ └── com_content ← component-level rules
│ └── #__content.42 ← an individual article's rules
- Every component, and optionally every item, can have its own asset row.
- Each row holds a JSON
rulescolumn that maps action to user group to allow or deny.
The standard component actions are:
| Action | Meaning |
|---|---|
core.admin |
Configure component options and permissions |
core.manage |
Access the admin screens |
core.create |
Create new items |
core.edit |
Edit any item |
core.edit.own |
Edit only items you authored |
core.edit.state |
Publish, unpublish, or archive |
core.delete |
Delete items |
Components check these permissions through the user object:
if (!$user->authorise('core.edit', 'com_content.article.42')) {
throw new \Exception(Text::_('JERROR_ALERTNOAUTHOR'), 403);
}
Permissions inherit down the asset tree: global to component to category to item.
Back to top5. The Developer Model
5.1 The Modern Entry Point: services/provider.php
Joomla 4 and later wire the component into a Dependency Injection (DI) container instead of using hard-coded includes. This is the single biggest change from "classic" Joomla. The file services/provider.php registers the factories the component needs and then builds the component object:
return new class () implements ServiceProviderInterface {
public function register(Container $container): void
{
// Register the factories the component needs:
$container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Content'));
$container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Content'));
$container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Content'));
$container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Content'));
// Build the component object, injecting its dependencies:
$container->set(ComponentInterface::class, function (Container $container) {
$component = new ContentComponent($container->get(ComponentDispatcherFactoryInterface::class));
$component->setMVCFactory($container->get(MVCFactoryInterface::class));
$component->setCategoryFactory($container->get(CategoryFactoryInterface::class));
$component->setRouterFactory($container->get(RouterFactoryInterface::class));
return $component;
});
}
};
This is a real example from com_content, lightly trimmed.
5.2 Old Way versus New Way
The change is easiest to understand by comparing the two styles side by side.
Classic (Joomla 1.5 to 3):
require_once 'controller.php';
$controller = new ContentController(); // tightly coupled, global state
$controller->execute(JFactory::getApplication()->input->get('task'));
Modern (Joomla 4 to 6):
// The MVCFactory builds controllers, models and views for you,
// injecting the database, input, user, dispatcher, etc.
$controller = $mvcFactory->createController('Article', 'Site', [], $app, $input);
Why the new way is better:
- Namespaced PSR-4 autoloading: no manual
requirestatements. - Testable: dependencies are injected, not fetched from global state.
- Consistent: every core component is wired the same way.
5.3 The Dispatcher
The Dispatcher is the component's front door at runtime. The ComponentDispatcherFactory builds it. It reads the request, enforces access, and routes to the right controller and task:
CMS App → ComponentInterface → Dispatcher → Controller → Model → View
Most components never write their own dispatcher. They inherit the default ComponentDispatcher. You only override it for special bootstrapping needs.
5.4 Routing and SEF URLs
The RouterFactory gives a component its own Router. The router converts between the internal query and the clean URL:
?option=com_content&view=article&id=42&catid=9
(internal query)
⇄
/news/9-events/42-summer-party
(clean SEF URL)
build()turns the query into URL segments, for the links you generate.parse()turns URL segments back into a query, for incoming requests.
Good routing is what makes a component's URLs friendly for both people and search engines.
5.5 Events: How Plugins Extend a Component
A component stays decoupled from the extensions that customise it by firing events at key moments. Plugins listen for these events. The component never needs to know who is listening:
// Inside the model's save(), com_content dispatches events:
$this->getDispatcher()->dispatch('onContentBeforeSave', $event);
// ... write to the database ...
$this->getDispatcher()->dispatch('onContentAfterSave', $event);
Common content events that any plugin can hook into:
| Event | Fires when |
|---|---|
onContentPrepare |
Before text is displayed (lets plugins transform it) |
onContentBeforeSave / onContentAfterSave |
Around saving an item |
onContentBeforeDelete / onContentAfterDelete |
Around deleting an item |
onContentChangeState |
An item is published or unpublished |
This is loose coupling. A plugin can add behaviour to com_content, or to your own component, without touching its code.
6. Shared Services
6.1 Components Rarely Work Alone
Modern components plug into CMS-wide services instead of reinventing them. This keeps the system consistent and saves a lot of duplicate code:
| Service | Component | What it gives any component |
|---|---|---|
| Categories | com_categories |
One shared, hierarchical category system |
| Custom Fields | com_fields |
Extra fields on items, no code required |
| Tags | com_tags |
Cross-component tagging |
| Workflow | com_workflow |
Editorial stages (draft to review to published) |
| Associations | com_associations |
Multilingual item linking |
6.2 Categories Are a Service, Not Part of com_content
This is an important insight that surprises many people. Categories do not belong to articles. They are a shared service that com_content opts in to:
// In services/provider.php - com_content OPTS IN to the shared category system:
$container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Content'));
...
$component->setCategoryFactory($container->get(CategoryFactoryInterface::class));
- One
#__categoriestable serves articles, contacts, banners, newsfeeds, and more. - Each component registers a CategoryFactory to consume it.
- Categories carry their own ACL, language, and nested-set hierarchy (the
lftandrgtcolumns).
Back to topCategories do not belong to articles. They are a shared service that articles, and many other components, simply use.
7. Working with Components
7.1 How to Read Any Component
When you want to understand a component, read its files in this order. The same map fits every core component and most third-party ones:
- The
*.xmlmanifest: what is it, what version, what files, what tables? services/provider.php: the entry point and which services it uses.src/Controller/: the available tasks.src/Model/: where the data comes from.tmpl/: what the user actually sees.
7.2 Overriding a Component the Right Way
Never edit core component files directly. They are overwritten on the next update, and your changes are lost. Instead, use the correct technique for what you need to change:
| Need | Correct technique |
|---|---|
| Change output or HTML | Template override: templates/<tpl>/html/com_xxx/<view>/<layout>.php |
| Change behaviour or react to events | Plugin hooking the component's events |
| Add data fields | Custom Fields (com_fields) |
| Change text strings | Language override in System → Languages → Overrides |
A template override is simply a copy of the layout file in a special location:
Copy: components/com_content/tmpl/article/default.php
To: templates/mytemplate/html/com_content/article/default.php
7.3 Building Your Own: The Minimum
A bare-bones working component needs surprisingly little:
com_hello/
├── hello.xml ← manifest
├── services/provider.php ← register with the DI container
├── src/
│ ├── Extension/HelloComponent.php
│ ├── Controller/DisplayController.php
│ ├── Model/HelloModel.php
│ └── View/Hello/HtmlView.php
└── tmpl/hello/default.php ← <h1>Hello, JUG!</h1>
The steps to get it running are:
- Zip it, then go to Extensions → Manage → Install.
- Joomla reads the manifest, copies the files, registers the namespace, and runs the install SQL.
- Visit
index.php?option=com_helloand your code runs.
A practical tip: start by copying a tiny core component (for example com_wrapper) and renaming it. You learn the structure faster from a working example than from a blank page.
7.4 Common Pitfalls
These are the mistakes that catch people most often when working with components:
- Forgetting the
<namespace>declaration in the manifest, which leads to a "class not found" error. - Some
<namespace>declaration was changed, but/administrator/cache/autoload_psr4.phpwas not refreshed. Delete the file and Joomla will recreate it automatically. - Editing core files directly, so changes are lost on the next update. Use overrides instead.
- Confusing a module (a small, reusable box) with a component (one per page).
- Putting business logic or SQL in the layout instead of the model.
- Re-inventing categories, fields, or tags instead of consuming the shared service.
8. Beyond the Browser: API and Performance
8.1 Web Services: The Headless Side of a Component
Since Joomla 4, a component can expose a REST API alongside its web pages. This works through a separate API application (/api/index.php) and the Web Services plugins:
/api/index.php/v1/content/articles ← list articles (GET)
/api/index.php/v1/content/articles/42 ← one article (GET / PATCH / DELETE)
/api/index.php/v1/users ← users endpoint
- A component adds an
src/Controller/Api*class, ansrc/View/.../JsonapiView, and anapi/folder. - It is enabled per-component by its Web Services plugin (for example
plg_webservices_content). - Authentication uses a token (the Joomla token plugin) or Basic authentication.
This is what makes headless Joomla possible. Mobile apps, single-page front-ends, and third-party integrations all talk to the same component.
8.2 Performance Considerations
Components are where most of a site's load lives, so they are where most of the tuning happens:
| Concern | What to do |
|---|---|
| Caching | Use Joomla's cache (the Cache API, view caching, onContentPrepare cache) for expensive queries |
| Query design | Build queries with the DatabaseQuery builder; select only the columns you need; paginate |
| Indexing | Make sure the database has indexes on catid, state, language, access, and ordering columns |
| ACL cost | Per-item asset checks add up; cache authorisation results where possible |
| Lazy loading | Defer heavy data (related items, fields) until it is actually rendered |
At scale, a single component may manage millions of rows, thousands of categories, and heavy ACL trees. Design the Model and the indexes for that from the start.
Back to top9. Common Mistakes and Pitfalls
9.1 Confusing Components with Modules
A component produces the one main content area of a page. A module is a small box that sits around it, and you can have many on one page. If you find yourself wanting "many of the same thing" on a page, you probably want a module, not a component.
9.2 Editing Core Files
Editing files inside components/com_content/ or the administrator equivalent feels quick, but Joomla overwrites those files on the next update. Always use a template override, a plugin, a custom field, or a language override instead.
9.3 Mixing Up the MVC Layers
The most common code smell is putting database queries or business logic inside a layout file in tmpl/. Keep SQL and logic in the Model, decisions in the Controller, and only display code in the Layout. This keeps the component testable and easy to override.
9.4 Reinventing Shared Services
Do not build your own category manager, field system, or tagging system. Joomla already provides com_categories, com_fields, and com_tags as shared services. Consuming them gives your component nesting, ACL, language support, and tooling for free.
9.5 Forgetting the Manifest Details
A missing <namespace> declaration causes "class not found" errors. A missing or wrong <files> or <sql> entry means files or tables are not installed. The manifest is small, but every line matters.
10. Best Practices
If you only remember a few things from this article, remember these:
- Exactly one component produces the main content of every page.
- Identify a component by its
com_prefix and theoption=URL parameter. - Read a component in order: manifest, then
provider.php, then Controller, Model, andtmpl/. - Never edit core files; use template overrides, plugins, custom fields, or language overrides.
- Keep SQL and logic in the Model, not in the Layout.
- Reuse shared services (Categories, Fields, Tags, Workflow) instead of building your own.
- Tune performance in the Model: cache expensive queries and add the right database indexes.
11. Quick Reference
IDENTIFY Look for option=com_xxx in the URL
SITE FILES components/com_xxx/
ADMIN FILES administrator/components/com_xxx/
ENTRY POINT services/provider.php (NOT controller.php)
STRUCTURE src/Controller, src/Model, src/View, src/Table, tmpl/
INSTALL ROW #__extensions (type=component, params=JSON Options)
OWN TABLES #__content, #__contact_details, #__banners, ...
PERMISSIONS #__assets (rules JSON, inherited down the tree)
OVERRIDE templates/{tpl}/html/com_xxx/{view}/{layout}.php
EXTEND Plugin hooking onContent* events
REST API /api/index.php/v1/... (enable Web Services plugin)
SHARED Categories, Fields, Tags, Workflow, Associations
Back to top12. Summary
In Joomla, a component is far more than just "the content in the middle". It is a complete mini-application that touches almost every layer of the system:
- Structure: built on the MVC pattern, with a clear folder layout.
- Wiring: connected to Joomla through a service container in
services/provider.php. - Data: it owns its own database tables and records itself in
#__extensions. - Security: it plugs into the central ACL through the
#__assetstable. - Extensibility: it fires events so plugins can extend it without changing its code.
- Integration: it consumes shared services like Categories, Fields, and Tags.
- Reach: it can go headless through the Web Services REST API.
The most useful idea to take home is this: learn the anatomy of one core component, and you can read, override, and extend almost all of them, whether they ship with Joomla or come from a third party.
If you are planning a custom Joomla feature, troubleshooting a component that behaves strangely, or thinking about exposing your data through an API, it pays to understand how components are built. They form the basis of every Joomla page.
Back to top

Joomla en Linux specialist voor snelle, veilige en schaalbare websites.


