Skip to main content

Components in Joomla

02 June 2026

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:

TypeRolePer pagePrefix
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:

ComponentPurpose
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.

Back to top

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:

ComponentSiteAdmin
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 top

3. 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.

Back to top

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:

ColumnFor 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:

ComponentCore 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 rules column that maps action to user group to allow or deny.

The standard component actions are:

ActionMeaning
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 top

5. 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 require statements.
  • 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:

EventFires 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.

Back to top

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:

ServiceComponentWhat 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 #__categories table 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 lft and rgt columns).

Categories do not belong to articles. They are a shared service that articles, and many other components, simply use.

Back to top

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:

  1. The *.xml manifest: what is it, what version, what files, what tables?
  2. services/provider.php: the entry point and which services it uses.
  3. src/Controller/: the available tasks.
  4. src/Model/: where the data comes from.
  5. 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:

NeedCorrect 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:

  1. Zip it, then go to Extensions → Manage → Install.
  2. Joomla reads the manifest, copies the files, registers the namespace, and runs the install SQL.
  3. Visit index.php?option=com_hello and 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.php was 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.
Back to top

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, an src/View/.../JsonapiView, and an api/ 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:

ConcernWhat 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 top

9. 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.

Back to top

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 the option= URL parameter.
  • Read a component in order: manifest, then provider.php, then Controller, Model, and tmpl/.
  • 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.
Back to top

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 top

12. 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 #__assets table.
  • 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
Components in Joomla
Peter Martin

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