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_instruments and #__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 bookings plugin 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:

  1. PATH_APP/components/{name}
  2. PATH_APP/components/com_{name}
  3. PATH_CORE/components/{name}
  4. 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 .ini files, where each is loaded from, and Lang::txt().
  • Assets — pushing CSS and JavaScript, and building image paths.
  • Routingrouter.php, build(), and parse().
  • Configurationconfig.xml, access.xml, and reading parameters back.
  • Packagingcomposer.json, the manifest, and what a distributable component contains.

Rewritten and checked against 2.4-main @ 348f0057c2 on 2026-09-10.