Developers
Components
A component is the largest of the extension types: a self-contained
application with its own controllers, views, models, database tables,
configuration, permissions, and URL space. com_blog, com_kb, and
com_resources are components. Each owns a top-level path on the site and
everything below it.
When you want one
Reach for a component when the thing you are adding is a page — when a user will navigate to it, it has records of its own, and someone has to administer them. A component owns the request, so only one runs per page.
Reach for something smaller when it does not:
| You want | Build |
|---|---|
| A screen with records behind it | a component |
| Something to happen when a record is saved, a user logs in, a group page is drawn | a plugin |
| A block in the sidebar of pages someone else owns | a module |
| A different look for pages that already exist | a template or a template override |
A component that only ever renders a box on other people's pages is a module wearing the wrong clothes, and it will fight the router for a URL it does not need.
The example this section builds
The pages that follow build one component, in the order you would build it:
com_bookings, which books a lab's instruments. It is small and it is
complete:
- two tables,
#__bookings_instrumentsand#__bookings_reservations; - an administrator face where a lab manager adds instruments and cancels reservations;
- a site face where a user picks an instrument, sees what is already booked, and reserves a slot;
- a
bookingsplugin group, so a hub can hook a reservation being created — to email the lab, or push it to a scheduling system — without editing the component.
com_bookings is the component being written. The code samples that are
included from the tree come from com_kb, the knowledge base: it is a real
shipped component small enough to read in an afternoon and complete enough to
show every piece — site, administrator and API clients, an ORM model, a
router, config.xml, access.xml, language files, and migrations. Where a
page shows com_kb, that is what the working code looks like today. Where it
shows com_bookings, that is what you write.
The same example runs through Database; the model, queries and table names there are the ones used here.
Register it, or it is invisible
This is the step most often missed, which is why Migrations is the first chapter rather than the last.
The smallest component that runs
Four files under app/components/com_bookings/ and one command:
app/components/com_bookings/
site/bookings.php
site/controllers/instruments.php
site/views/instruments/tmpl/display.php
migrations/Migration20260901000000ComBookings.php
site/bookings.php — the entry point. It is required, so it runs at the top
level; it names a controller and executes it:
namespace Components\Bookings\Site;
defined('_HZEXEC_') or die();
$controller = new Controllers\Instruments();
$controller->execute();
site/controllers/instruments.php — one class, one task:
namespace Components\Bookings\Site\Controllers;
use Hubzero\Component\SiteController;
class Instruments extends SiteController
{
public function displayTask()
{
$this->view->display();
}
}
site/views/instruments/tmpl/display.php — the layout the task renders,
chosen by name and wired up by nothing:
<?php defined('_HZEXEC_') or die(); ?>
<h2>Instruments</h2>
Then register it:
php core/bin/muse migration -f -e=com_bookings
/index.php?option=com_bookings now renders the heading. Everything in the
rest of this section is added on top of those four files.
Where components live
There are two roots. core/components/ holds what the platform ships.
app/components/ holds what a single hub adds, and it wins. Component::path()
in Hubzero\Component\Loader
tries four directories and takes the first that exists:
PATH_APP/components/{name}PATH_APP/components/com_{name}PATH_CORE/components/{name}PATH_CORE/components/com_{name}
Whichever directory it finds owns the component outright. Putting
app/components/com_kb/ beside the shipped core/components/com_kb/ replaces
it wholesale; the two trees are never merged, so a partial copy is a broken
copy. The same rule governs class loading — see
Structure.
Write your own component into app/. core/ is the platform's, and an
upgrade replaces it.
The three clients
A component serves up to three applications, and each gets its own subdirectory:
| Directory | Application | Entry point |
|---|---|---|
site/ |
the public hub | site/{name}.php |
admin/ |
the administrator at /administrator |
admin/{name}.php |
api/ |
the JSON API at /api |
api/controllers/{name}v{major}_{minor}.php |
Controllers, views, assets, and language files belong to one client and live
inside its directory. Models, config/, and migrations/ sit above the client
directories because all three clients share them.
None of the three is required. A component with only site/ is perfectly
valid; so is one with only api/. com_bookings grows a site/ first, then
an admin/, and never needs an api/.
How a request reaches your code
The site and administrator applications route a request to a single component,
identified by the option query variable, then call
Component::render('com_bookings'). That method defines PATH_COMPONENT (the
client directory), loads the component's language files, and executes the entry
point. The entry point picks a controller class, constructs it, and calls
execute(). The controller runs a task, hands data to a view, and the view
renders a layout. See Controllers and Views.
The API application takes a different path through
Hubzero\Api\Component\Loader,
which resolves a versioned controller class and calls execute() on it
directly. There is no view layer; responses are set on the response object.
Naming
Every name in a component derives from one word. For com_bookings that word
is bookings:
| Thing | Form |
|---|---|
| Directory | com_bookings |
| Entry points | site/bookings.php, admin/bookings.php |
| Namespace | Components\Bookings |
| Table prefix | #__bookings_ |
| Language files | en-GB.com_bookings.ini |
| Migration classes | Migration20260901000000ComBookings |
| Language keys | COM_BOOKINGS_* |
Throughout these pages, {ComponentName} stands for the studly-cased name —
Bookings, Kb, Blog — and {componentname} for its lowercase form. The
same pairing applies to {ControllerName}, {ViewName}, and {ModelName}.
In this section
- Migrations — registering the component and creating its tables. Start here.
- Structure — the directory layout, the namespaces, and the entry point.
- Controllers — tasks, the task map, and the site, administrator, and API base classes.
- Helpers — shared static classes, and helpers callable from a view.
- Models — the ORM models and plain classes that hold a component's data and rules.
- Views — view objects, layouts, the search order, and template overrides.
- Languages — the
.inifiles, where each is loaded from, andLang::txt(). - Assets — pushing CSS and JavaScript, and building image paths.
- Routing —
router.php,build(), andparse(). - Configuration —
config.xml,access.xml, and reading parameters back. - Packaging —
composer.json, the manifest, and what a distributable component contains.
Rewritten and checked against 2.4-main @ 348f0057c2 on 2026-09-10.