joomla/joomla-cms
Joomla is a free, open-source content management system built on PHP. It lets you build and manage websites and online applications with extensible components, modules, plugins, templates, multilingual support, and flexible access control, backed by a large global community.
joomla/joomla-cms is the full CMS monorepo. Fastest path is usually a release package or:
composer create-project joomla/joomla-cms my-site
# then run the CLI installer (Joomla 4/5+)
php installation/joomla.php install
From a git clone, run composer install first and point the document root at the repo root; the installer lives under /installation.administrator/components/ (back-end extensions), components/ (front-end extensions), modules/, plugins/, templates/, libraries/src/ (the Joomla\CMS framework), and api/components/ for Web Services endpoints.templates/<your-template>/html/com_content/article/default.php) teaches you the output layer without touching core. A module (mod_hello/mod_hello.php + tmpl/default.php + mod_hello.xml) teaches the manifest, install, and rendering lifecycle in under an hour.system plugin to hook application events, or a small component with a list/edit view using Joomla's MVC bases. Both are representative of daily extension work and force you through manifest, language files, ACL, and routing.administrator/logs/. Use the in-built Web Services (/api/index.php/v1/...) if you need to integrate external systems rather than writing new controllers.components/com_foo/ plus administrator/components/com_foo/, an entry point, a services/provider.php DI registration, MVC classes, and tmpl/ views. Modules are entry script + tmpl/; plugins are event subscribers; templates are layout/override packages.<namespace path="src">Vendor\Component\Foo</namespace>
Register services in services/provider.php using Joomla\CMS\Extension\Service\Provider\... and the component's BootableExtensionInterface. Avoid loading files manually.$app = \Joomla\CMS\Factory::getApplication();
$db = \Joomla\CMS\Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
Inject dependencies into controllers/models/views through the container and constructor arguments where possible.BaseController, BaseModel, HtmlView (or JsonView for APIs). Keep business logic in models, formatting in views, request validation in controllers. Use FormModel + XML forms in models/forms/ for consistent filtering, validation, and ACL-aware field rendering.Joomla\Event\SubscriberInterface and register in the plugin entry point:
$dispatcher->addSubscriber(new MyPlugin($app));
Common hooks: onAfterInitialise, onAfterRoute, onAfterDispatch, onContentPrepare, onContentBeforeSave, onUserAfterLogin. Dispatch custom events for your own extensibility.$db->getQuery(true) and #__ prefixes; never concatenate table names. Use prepared statements / quoteName to keep queries portable and safe:
$query = $db->getQuery(true)
->select($db->quoteName(['id', 'title']))
->from($db->quoteName('#__content'))
->where($db->quoteName('state') . ' = 1');
access.xml, create them on install via script.php (#__assets), and gate controllers/views with $user->authorise('core.edit', 'com_foo'). Use $app->checkToken() / Session::checkToken('post') on every state-changing request.com_ajax for lightweight endpoints. For small JSON/AJAX handlers, a module or plugin registered as com_ajax is often faster than a new component:
index.php?option=com_ajax&module=foo&format=json
You still get framework bootstrapping, ACL, and output formatting for free.libraries/.plugins/fields/*; reuse the Form API (Joomla\CMS\Form\Form) for consistent validation, filtering, and layout. This keeps admin UX native.services/provider.php with ConsoleApplication and Joomla\Console\Command\AbstractCommand. Joomla 4.1+ also has plg_system_scheduler for cron-like job definitions — good for recurring imports/syncs.update.xml and a <server> entry so the native updater can handle upgrades. Use Joomla's build/ tooling for release packages; keep install scripts (script.php) idempotent for migrations.tests/Unit (PHPUnit); system/acceptance tests use Codeception + Selenium. For your own extension, mirror this structure and mock Factory::getApplication() and the database.libraries/ and administrator/components/com_* as read-only.checkToken() or a stale token produces a 403 with little detail. Include HTMLHelper::_('form.token') in forms and check it server-side. Same for AJAX: pass the token and verify in the handler.manifest.xml can install "successfully" while registering nothing. Validate against the Joomla schema, confirm <namespace> and <autoload> entries, and check #__extensions after install.script.php didn't run or rules weren't inserted into #__assets, permissions behave oddly. Re-check the asset tree on update, not just install.Text::_('COM_FOO_...') and ship language/en-GB/en-GB.com_foo.ini plus .sys.ini. Missing strings show the raw key and look broken in admin.J* legacy classes; 5.x continues namespacing and drops deprecated APIs. Pin your target Joomla version in composer.json/manifest <targetplatform>, and test against the next major before release. The project ships frequent minor releases, so follow the release notes rather than assuming LTS stability.router.php) and menu-item aliases interact; test with SEF enabled and disabled. Missing router logic yields ?option=com_foo&view=... URLs that break menus and canonical links.#__ prefix; MySQL-specific SQL will fail on PostgreSQL. Check date/time handling against the configured timezone (Factory::getConfig()->get('offset')).htmlspecialchars / HTMLHelper::_('string.truncate') and InputFilter rather than trusting request data. Joomla's filter defaults differ by context, so be explicit with $app->getInput()->get('x', '', 'cmd').JDEBUG, the Debug plugin's query/deprecation panels, $app->enqueueMessage(), and Monolog under administrator/logs/ are your first stops. var_dump in an HTML view is noisy — use the JSON view or log channel.tmp/ and administrator/logs/ must be writable; configuration.php should be read-only in production. Bad permissions cause confusing upload, cache, and install failures.com_ajax requires a published module/plugin and the correct module/plugin parameter; unpublished means a flat 404. It's a frequent "endpoint disappeared" cause.vendor/; adding third-party PHP dependencies means managing autoload paths and version conflicts with Joomla's libraries/vendor. Keep extension dependencies isolated, and check composer.json constraints before adding large libraries.How can I help you explore Laravel packages today?