joomla-conventions

star 7

Joomla 4/5 component development with MVC, services, plugins, and language handling

zb-ss By zb-ss schedule Updated 2/9/2026

name: joomla-conventions description: Joomla 4/5 component development with MVC, services, plugins, and language handling license: MIT compatibility: opencode metadata: cms: joomla version: "4.0+"

Component Structure (Joomla 4/5)

com_example/
├── administrator/
│   ├── forms/
│   │   └── item.xml
│   ├── services/
│   │   └── provider.php
│   ├── src/
│   │   ├── Controller/
│   │   ├── Extension/
│   │   ├── Field/
│   │   ├── Helper/
│   │   ├── Model/
│   │   ├── Service/
│   │   ├── Table/
│   │   └── View/
│   └── tmpl/
│       └── items/
├── site/
│   ├── src/
│   └── tmpl/
├── media/
│   ├── css/
│   └── js/
└── language/
    └── en-GB/

Service Provider

<?php

declare(strict_types=1);

namespace Joomla\Component\Example\Administrator\Extension;

use Joomla\CMS\Extension\ComponentInterface;
use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory;
use Joomla\CMS\Extension\Service\Provider\MVCFactory;
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 MVCFactory('\\Joomla\\Component\\Example'));
        $container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Example'));
        
        $container->set(
            ComponentInterface::class,
            function (Container $container) {
                $component = new ExampleComponent($container->get(ComponentDispatcherFactoryInterface::class));
                $component->setMVCFactory($container->get(MVCFactoryInterface::class));
                return $component;
            }
        );
    }
};

Model (ListModel)

<?php

declare(strict_types=1);

namespace Joomla\Component\Example\Administrator\Model;

use Joomla\CMS\MVC\Model\ListModel;
use Joomla\Database\QueryInterface;

class ItemsModel extends ListModel
{
    protected function getListQuery(): QueryInterface
    {
        $db = $this->getDatabase();
        $query = $db->getQuery(true);
        
        $query->select($db->quoteName(['a.id', 'a.title', 'a.state']))
            ->from($db->quoteName('#__example_items', 'a'));
        
        // Filter by state
        $state = $this->getState('filter.state');
        if (is_numeric($state)) {
            $query->where($db->quoteName('a.state') . ' = :state')
                ->bind(':state', $state, ParameterType::INTEGER);
        }
        
        return $query;
    }
}

Table Class

<?php

declare(strict_types=1);

namespace Joomla\Component\Example\Administrator\Table;

use Joomla\CMS\Table\Table;
use Joomla\Database\DatabaseDriver;

class ItemTable extends Table
{
    public function __construct(DatabaseDriver $db)
    {
        parent::__construct('#__example_items', 'id', $db);
    }
    
    public function check(): bool
    {
        if (empty($this->title)) {
            $this->setError(Text::_('COM_EXAMPLE_ERROR_TITLE_REQUIRED'));
            return false;
        }
        
        return true;
    }
}

Security - Database Queries

// ALWAYS use parameter binding
$query->where($db->quoteName('id') . ' = :id')
    ->bind(':id', $id, ParameterType::INTEGER);

// For strings
$query->where($db->quoteName('alias') . ' = :alias')
    ->bind(':alias', $alias, ParameterType::STRING);

// NEVER do this:
// $query->where('id = ' . $id); // SQL INJECTION!

Language Strings

// In PHP
use Joomla\CMS\Language\Text;

echo Text::_('COM_EXAMPLE_TITLE');
echo Text::sprintf('COM_EXAMPLE_ITEMS_COUNT', $count);
echo Text::plural('COM_EXAMPLE_N_ITEMS_DELETED', $count);

// In templates
<?php echo Text::_('COM_EXAMPLE_SUBMIT'); ?>

Language file (language/en-GB/com_example.ini):

COM_EXAMPLE_TITLE="Example Component"
COM_EXAMPLE_ITEMS_COUNT="Found %d items"
COM_EXAMPLE_N_ITEMS_DELETED="1 item deleted"
COM_EXAMPLE_N_ITEMS_DELETED_1="1 item deleted"
COM_EXAMPLE_N_ITEMS_DELETED_MORE="%d items deleted"

Forms (XML)

<?xml version="1.0" encoding="UTF-8"?>
<form>
    <fieldset name="details">
        <field
            name="title"
            type="text"
            label="COM_EXAMPLE_FIELD_TITLE_LABEL"
            required="true"
            maxlength="255"
        />
        <field
            name="state"
            type="list"
            label="JSTATUS"
            default="1"
        >
            <option value="1">JPUBLISHED</option>
            <option value="0">JUNPUBLISHED</option>
        </field>
    </fieldset>
</form>

Plugin Structure

<?php

declare(strict_types=1);

namespace Joomla\Plugin\System\Example\Extension;

use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\Event\SubscriberInterface;

final class Example extends CMSPlugin implements SubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            'onAfterInitialise' => 'onAfterInitialise',
        ];
    }
    
    public function onAfterInitialise(): void
    {
        // Plugin logic here
    }
}

Permission Checks

use Joomla\CMS\Factory;

$user = Factory::getApplication()->getIdentity();

if (!$user->authorise('core.edit', 'com_example')) {
    throw new \Exception(Text::_('JERROR_ALERTNOAUTHOR'), 403);
}

CSRF Token

// In form
<?php echo HTMLHelper::_('form.token'); ?>

// In controller
if (!Session::checkToken()) {
    throw new \Exception(Text::_('JINVALID_TOKEN'), 403);
}
Install via CLI
npx skills add https://github.com/zb-ss/opencode-workflows --skill joomla-conventions
Repository Details
star Stars 7
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator