This repository contains best practices, scripts, and documentation for Joomla component and package development. It is designed to be included as a submodule in other Joomla projects.
build-package.bat: Batch script for building Joomla packagesPACKAGE-BUILD-NOTES.md: Notes and troubleshooting for package creationJOOMLA5-CHECKLIST.md: Checklist for Joomla 5 developmentJOOMLA6-CHECKLIST.md: Checklist for Joomla 6 development- Additional best practices files
- Use only Joomla native libraries (no third-party dependencies)
- Minimum PHP version: 8.3.0 for Joomla 6, 8.1.0 for Joomla 5
- Modern namespace usage:
use Joomla\CMS\Factoryinstead ofJFactory - Modern event system: Use
SubscriberInterfacefor plugins - Asset management: Use Joomla's Web Asset Manager for CSS/JS
- File operations: Use
Joomla\CMS\Filesystem\FileandJoomla\CMS\Filesystem\Folder - Database: Use
Joomla\Database\DatabaseInterfaceandJoomla\CMS\Factory::getDbo() - Input handling: Use
Factory::getApplication()->getInput() - Manifest version: Use
version="6.0"for Joomla 6,version="5.0"for Joomla 5 - Only the package manifest should declare
<updateservers> - Structured exception handling and logging: Use
Joomla\CMS\Log\Log - Log path:
administrator/logs/com_stageit.log.php - AJAX error display: Show full stack traces in Joomla's message container, formatted in monospace
- No alert() popups: Use Bootstrap alerts in the system message container
- Build scripts: Use PowerShell or batch scripts that preserve file encoding and use forward slashes in ZIPs
- Installation script: Add cleanup code in
postflight()to remove old/conflicting files and update sites - Changelog formats: Maintain both
CHANGELOG.mdandCHANGELOG.html(HTML must be article-ready without head/body tags) - Versioning: Use semantic versioning (MAJOR.MINOR.PATCH)
- Maintain a development checklist for each Joomla version
- Language files are MANDATORY: All extensions MUST use Joomla's core language system for all user-facing text
- Custom CSS tab: All modules MUST include a dedicated tab/fieldset for custom CSS to allow users to add styling without template overrides
- Enhanced multi-select fields: Use
layout="joomla.form.field.list-fancy-select"for tag-style multi-select with removable chips (Joomla 5+)
CRITICAL: All Joomla extensions MUST use the core Joomla language system. Never hardcode user-facing text.
ALWAYS use language constants in XML manifests:
<!-- CORRECT: Use language constant -->
<name>MOD_MYMODULE</name>
<description>MOD_MYMODULE_XML_DESCRIPTION</description>
<!-- WRONG: Never hardcode -->
<name>My Module</name>
<description>This is my module</description>Modules (site):
language/
en-GB/
en-GB.mod_modulename.ini
Components:
language/
en-GB/
en-GB.com_componentname.ini # Site language
admin/language/
en-GB/
en-GB.com_componentname.ini # Admin language
en-GB.com_componentname.sys.ini # System language (installer, menu)
Plugins:
language/
en-GB/
en-GB.plg_plugintype_pluginname.ini
en-GB.plg_plugintype_pluginname.sys.ini
Required format (UTF-8 without BOM):
; Joomla! Project
; Copyright (C) 2025 Your Name. All rights reserved.
; License GNU General Public License version 2 or later; see LICENSE.txt
; Note : All ini files need to be saved as UTF-8
MOD_MYMODULE="My Module"
MOD_MYMODULE_XML_DESCRIPTION="Description of what the module does."
MOD_MYMODULE_FIELD_LABEL="Field Label"
MOD_MYMODULE_FIELD_DESC="Field description text."Naming conventions:
- Extension name:
MOD_MODULENAME,COM_COMPONENTNAME,PLG_TYPE_NAME - Description: Add
_XML_DESCRIPTIONsuffix - Field labels: Add
_LABELsuffix - Field descriptions: Add
_DESCsuffix - Use UPPERCASE with underscores
Always declare language files in your manifest:
<languages>
<language tag="en-GB">language/en-GB/en-GB.mod_modulename.ini</language>
</languages>In modules:
use Joomla\CMS\Factory;
$app = Factory::getApplication();
$lang = $app->getLanguage();
$lang->load('mod_modulename', dirname(__FILE__));
// Use language strings
echo Text::_('MOD_MODULENAME_SOME_TEXT');In components and plugins, language is loaded automatically.
use Joomla\CMS\Language\Text;
// Simple translation
echo Text::_('MOD_MYMODULE_TITLE');
// With sprintf formatting
echo Text::sprintf('MOD_MYMODULE_COUNT', $count);
// Plural handling
echo Text::plural('MOD_MYMODULE_N_ITEMS', $count);Use existing Joomla constants when appropriate:
<option value="1">JYES</option>
<option value="0">JNO</option>
<!-- Other common constants -->
JGLOBAL_DESCRIPTION
JFIELD_PUBLISHED_LABEL
JFIELD_PUBLISHED_DESC
JFIELD_ORDERING_LABEL
JFIELD_BASIC_LABEL
JFIELD_CONFIG_ADVANCED_LABEL
JFIELD_ALT_LAYOUT_LABEL
COM_MODULES_FIELD_MODULECLASS_SFX_LABELCRITICAL: All language files MUST be UTF-8 encoded:
- Save as UTF-8 without BOM (Byte Order Mark)
- Test before packaging: Open in a hex editor to verify no BOM (
EF BB BFat start) - Git configuration: Ensure
.gitattributeshandles line endings correctly - Special characters: Use actual UTF-8 characters, not HTML entities
Before releasing any extension, verify:
- All XML
<name>tags use language constants (not hardcoded text) - All XML
<description>tags use language constants - All field labels and descriptions use language constants
- Language files are UTF-8 without BOM
- Language files follow proper naming conventions
- Language files are declared in the manifest
- No hardcoded user-facing text in PHP code
- Test installation shows proper translated text (not language keys)
β DON'T:
<name>mod_mymodule</name> <!-- Shows technical name to users -->
<name>My Module</name> <!-- Not translatable -->β DO:
<name>MOD_MYMODULE</name>In language file:
MOD_MYMODULE="My Module"MANDATORY: All Joomla modules MUST include a dedicated Custom CSS tab to allow users to add styling without creating template overrides.
- User Flexibility: Users can customize styling without modifying template files
- Instance-Specific Styling: Different module instances can have unique styles
- Update-Safe: Custom CSS persists through module updates
- No Template Override Required: Reduces technical barriers for users
- Professional Standard: Expected feature in modern Joomla extensions
Add a dedicated fieldset for custom CSS in your module XML:
<fieldset name="custom_css" label="MOD_MODULENAME_CUSTOM_CSS_LABEL">
<field
name="custom_css"
type="textarea"
label="MOD_MODULENAME_CUSTOM_CSS_FIELD_LABEL"
description="MOD_MODULENAME_CUSTOM_CSS_FIELD_DESC"
rows="10"
cols="50"
filter="raw"
/>
</fieldset>Important: Use filter="raw" to allow CSS syntax without sanitization.
Add these to your language file:
MOD_MODULENAME_CUSTOM_CSS_LABEL="Custom CSS"
MOD_MODULENAME_CUSTOM_CSS_FIELD_LABEL="CSS Code"
MOD_MODULENAME_CUSTOM_CSS_FIELD_DESC="Add custom CSS styles for this module. CSS will be scoped to this module instance."In your module template (e.g., tmpl/default.php):
// Get custom CSS parameter
$customCss = $params->get('custom_css', '');
$moduleId = (int) $module->id;
$wrapperId = 'mod-modulename-' . $moduleId;
?>
<style>
/* Your module's default styles */
#<?php echo $wrapperId; ?> .some-class {
/* styles */
}
<?php if (!empty($customCss)) : ?>
/* Custom CSS */
<?php echo $customCss; ?>
<?php endif; ?>
</style>
<div id="<?php echo $wrapperId; ?>" class="mod-modulename">
<!-- Module content -->
</div>Always use a unique ID based on the module ID:
$wrapperId = 'mod-modulename-' . (int) $module->id;This allows:
- Multiple instances with different custom CSS
- Specific targeting without affecting other modules
- Clean CSS specificity
Place custom CSS at the end of your style block so it can override default styles:
<style>
/* Default module styles first */
<?php if (!empty($customCss)) : ?>
/* Custom CSS last - highest specificity */
<?php echo $customCss; ?>
<?php endif; ?>
</style>In the field description, help users understand scoping:
MOD_MODULENAME_CUSTOM_CSS_FIELD_DESC="Add custom CSS styles for this module. Use #mod-modulename-<?php echo $module->id; ?> as the selector prefix to scope styles to this instance."Consider adding helpful comments in the placeholder or description:
<field
name="custom_css"
type="textarea"
label="MOD_MODULENAME_CUSTOM_CSS_FIELD_LABEL"
description="MOD_MODULENAME_CUSTOM_CSS_FIELD_DESC"
hint="Example: #mod-modulename-123 .item { color: red; }"
rows="10"
cols="50"
filter="raw"
/>While filter="raw" is necessary for CSS, be aware:
- Only for CSS: Never use
filter="raw"for user-input fields that output HTML - Administrator Access Only: Module parameters are only editable by administrators
- Output in Style Tags: CSS is output within
<style>tags, not executable HTML - No JavaScript: Users should add CSS only, not
<script>tags
For extra security, you can sanitize CSS:
// Basic CSS sanitization (strips script tags and dangerous patterns)
$customCss = $params->get('custom_css', '');
$customCss = strip_tags($customCss);
$customCss = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $customCss);XML Manifest (mod_example.xml):
<config>
<fields name="params">
<fieldset name="basic" label="JFIELD_BASIC_LABEL">
<!-- Your basic fields -->
</fieldset>
<fieldset name="custom_css" label="MOD_EXAMPLE_CUSTOM_CSS_LABEL">
<field
name="custom_css"
type="textarea"
label="MOD_EXAMPLE_CUSTOM_CSS_FIELD_LABEL"
description="MOD_EXAMPLE_CUSTOM_CSS_FIELD_DESC"
rows="10"
cols="50"
filter="raw"
/>
</fieldset>
<fieldset name="advanced" label="JFIELD_CONFIG_ADVANCED_LABEL">
<!-- Advanced fields -->
</fieldset>
</fields>
</config>Language File (en-GB.mod_example.ini):
MOD_EXAMPLE_CUSTOM_CSS_LABEL="Custom CSS"
MOD_EXAMPLE_CUSTOM_CSS_FIELD_LABEL="CSS Code"
MOD_EXAMPLE_CUSTOM_CSS_FIELD_DESC="Add custom CSS styles for this module. Use the module wrapper ID to scope your styles."Template File (tmpl/default.php):
<?php
defined('_JEXEC') or die;
$moduleId = (int) $module->id;
$wrapperId = 'mod-example-' . $moduleId;
$customCss = $params->get('custom_css', '');
?>
<style>
#<?php echo $wrapperId; ?> {
/* Default module styles */
}
<?php if (!empty($customCss)) : ?>
/* Custom CSS */
<?php echo $customCss; ?>
<?php endif; ?>
</style>
<div id="<?php echo $wrapperId; ?>" class="mod-example">
<!-- Module content -->
</div>Before releasing a module, verify:
- Custom CSS fieldset added to XML manifest
- Field uses
filter="raw"attribute - Language strings defined for tab and field
- Custom CSS loaded in template with
$params->get('custom_css', '') - Custom CSS output at end of style block
- Module uses unique ID based on
$module->id - Field description explains how to scope styles
- Tested with actual CSS to verify functionality
MANDATORY: Use Joomla's native fancy-select layout for all multi-select fields to provide modern UX with tag-style interface.
- Better UX: Tag/chip interface with removable badges instead of scrolling list boxes
- Searchable: Users can type to filter options quickly
- Native Joomla: Uses built-in web components (no custom code needed)
- Consistent: Matches Joomla's admin interface standards
- Accessible: Better keyboard navigation and screen reader support
For any field with multiple="true", use the fancy-select layout:
<field
name="parent_ids"
type="category"
extension="com_content"
label="MOD_MODULENAME_PARENT_IDS_LABEL"
description="MOD_MODULENAME_PARENT_IDS_DESC"
multiple="true"
layout="joomla.form.field.list-fancy-select"
showon="selection_mode:multiple_parents"
/>Important attributes:
multiple="true"- Required for multi-selectlayout="joomla.form.field.list-fancy-select"- Enables fancy-select UI- No
sizeorclassattributes needed
In your main module file (e.g., mod_modulename.php), load the fancy-select web component:
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\ModuleHelper;
// Load fancy-select for enhanced multi-select in admin
$app = Factory::getApplication();
if ($app->isClient('administrator')) {
$wa = $app->getDocument()->getWebAssetManager();
$wa->useScript('webcomponent.field-fancy-select');
}
// ... rest of module codeThe fancy-select layout works with:
type="category"- Category selectiontype="list"- Custom option liststype="sql"- Database-driven lists- Any field type that extends
ListField
Without fancy-select (old way):
βββββββββββββββββββββββββββ
β Category 1 β
β Category 2 β
β Category 3 β
β Category 4 β β Scroll to see more
β Category 5 β
βββββββββββββββββββββββββββ
With fancy-select (correct way):
βββββββββββββββββββββββββββββββββββββββββββββββ
β [Category 1 Γ] [Category 3 Γ] [Type to...] β β Selected as removable chips
βββββββββββββββββββββββββββββββββββββββββββββββ
β (Click to see dropdown)
βββββββββββββββββββββββββββββββββββββββββββββββ
β Category 2 β
β Category 4 β
β Category 5 β
βββββββββββββββββββββββββββββββββββββββββββββββ
- Select: Click an item from dropdown or type to search
- Remove: Click Γ on any chip to deselect
- Search: Type in box to filter options
- Keyboard: Arrow keys, Enter to select, Backspace to remove last item
XML Manifest (mod_example.xml):
<config>
<fields name="params">
<fieldset name="basic" label="JFIELD_BASIC_LABEL">
<field
name="categories"
type="category"
extension="com_content"
label="MOD_EXAMPLE_CATEGORIES_LABEL"
description="MOD_EXAMPLE_CATEGORIES_DESC"
multiple="true"
layout="joomla.form.field.list-fancy-select"
/>
</fieldset>
</fields>
</config>Module File (mod_example.php):
<?php
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Helper\ModuleHelper;
// Load fancy-select for enhanced multi-select in admin
$app = Factory::getApplication();
if ($app->isClient('administrator')) {
$wa = $app->getDocument()->getWebAssetManager();
$wa->useScript('webcomponent.field-fancy-select');
}
// Module logic here
require ModuleHelper::getLayoutPath('mod_example', $params->get('layout', 'default'));Before releasing an extension with multi-select fields, verify:
- All multi-select fields use
layout="joomla.form.field.list-fancy-select" - Web asset loaded in module PHP file with admin client check
- No
class="advancedSelect"or custom CSS classes (not needed) - No custom JavaScript for select enhancement (Joomla handles it)
- Tested in administrator: chips appear with Γ buttons
- Search/filter works when typing in field
- Selected items can be removed by clicking Γ
β DON'T:
<!-- Old/wrong approaches -->
<field multiple="true" class="advancedSelect" />
<field multiple="true" size="10" class="chosen" />
<field multiple="true" type="list" /> <!-- Missing layout -->β DO:
<field
multiple="true"
layout="joomla.form.field.list-fancy-select"
/>BEST PRACTICE: Modules should output nothing (not even wrapper HTML) when they have no content to display.
- Clean markup: No empty divs cluttering the page
- SEO: Reduces empty markup that search engines must parse
- Performance: Fewer DOM nodes to render
- Better UX: No visual artifacts from empty containers
Wrap the entire module output (including wrapper div) in a conditional check:
<?php
// Get data at the top
$categories = CategoryGridHelper::getCategories($params);
?>
<!-- ALL CSS in <style> block BEFORE conditional check -->
<style>
/* Module styles here */
</style>
<!-- Conditional wrapper around ALL HTML output -->
<?php if (!empty($categories)) : ?>
<div id="<?php echo $wrapperId; ?>" class="mod-modulename">
<!-- Module content here -->
</div>
<?php if ($enableFeature) : ?>
<script>
// JavaScript if needed
</script>
<?php endif; ?>
<?php endif; ?>Key points:
- Check for content BEFORE opening the wrapper div
- Keep CSS in
<style>blocks outside the conditional (CSS won't render if no content) - Close the conditional AFTER all module HTML including scripts
- Use
!empty($data)to check for content
β DON'T:
<div class="mod-modulename">
<?php if (empty($categories)) : ?>
<p>No items to display.</p> <!-- Shows empty message -->
<?php else : ?>
<!-- Content -->
<?php endif; ?>
</div> <!-- Always outputs wrapper -->β DO:
<?php if (!empty($categories)) : ?>
<div class="mod-modulename">
<!-- Content -->
</div>
<?php endif; ?> <!-- No output at all if empty -->COMMON NEED: Extract images from Joomla category descriptions, article intros, or other HTML content fields.
Use this pattern to extract the first image from HTML content:
// Extract first image from description
$image = '';
if (!empty($category->description))
{
// Match img tags in the description
if (preg_match('/<img[^>]+src=["\']([^"\']+)["\'][^>]*>/i', $category->description, $matches))
{
$image = $matches[1];
}
}Pattern breakdown:
<img[^>]+: Match opening img tag with any attributessrc=["\']: Match src attribute with quote (single or double)([^"\']+): Capture group for the URL (anything except quotes)["\']: Closing quote[^>]*>: Rest of img tagiflag: Case-insensitive matching
PATTERN: Give users control over which image to use with a priority/fallback system.
Example implementation:
// Get image setting
$imageSource = $params->get('image_source', 'core_first');
// Get both possible images
$coreImage = $paramsObj->get('image', ''); // Core category image
$descImage = ''; // Extract from description (see pattern above)
// Determine which to use based on priority
$finalImage = '';
switch ($imageSource)
{
case 'core_only':
$finalImage = $coreImage;
break;
case 'description_only':
$finalImage = $descImage;
break;
case 'core_first':
$finalImage = !empty($coreImage) ? $coreImage : $descImage;
break;
case 'description_first':
$finalImage = !empty($descImage) ? $descImage : $coreImage;
break;
}XML configuration field:
<field
name="image_source"
type="list"
label="MOD_MODULENAME_IMAGE_SOURCE_LABEL"
description="MOD_MODULENAME_IMAGE_SOURCE_DESC"
default="core_first"
>
<option value="core_only">MOD_MODULENAME_IMAGE_SOURCE_CORE_ONLY</option>
<option value="description_only">MOD_MODULENAME_IMAGE_SOURCE_DESC_ONLY</option>
<option value="core_first">MOD_MODULENAME_IMAGE_SOURCE_CORE_FIRST</option>
<option value="description_first">MOD_MODULENAME_IMAGE_SOURCE_DESC_FIRST</option>
</field>Language strings:
MOD_MODULENAME_IMAGE_SOURCE_LABEL="Image source"
MOD_MODULENAME_IMAGE_SOURCE_DESC="Choose which image to use for each item."
MOD_MODULENAME_IMAGE_SOURCE_CORE_ONLY="Core image only"
MOD_MODULENAME_IMAGE_SOURCE_DESC_ONLY="First image in description only"
MOD_MODULENAME_IMAGE_SOURCE_CORE_FIRST="Core image first, then description image"
MOD_MODULENAME_IMAGE_SOURCE_DESC_FIRST="Description image first, then core image"Categories:
- Core category image vs. first image in category description
- Useful when users paste rich content into descriptions
Articles:
- Intro image vs. first image in intro text
- Full image vs. first image in full text
Custom Components:
- Any field with HTML content that may contain images
- Providing flexibility for content editors
PATTERN: Images should adapt to container width, especially in single-column mobile layouts.
/* Desktop: Fixed dimensions */
#module-id .image-wrapper img {
display: block;
width: 100%;
height: auto;
max-height: 200px; /* From module settings */
object-fit: cover;
}
/* Mobile: Remove height constraints */
@media (max-width: 600px) {
#module-id .grid-item {
width: 100%; /* Full width */
}
#module-id .image-wrapper img {
max-height: none; /* Remove constraint */
height: auto; /* Natural aspect ratio */
}
}Key points:
- Use
max-heightfor desktop to constrain images - Use
object-fit: coverto maintain aspect ratio while filling container - Remove
max-heightconstraint on mobile for better responsiveness - Always set
width: 100%for responsive behavior
MANDATORY: All extensions MUST maintain both CHANGELOG.md and CHANGELOG.html files.
- CHANGELOG.md: For developers, version control, and GitHub display
- CHANGELOG.html: For end-users and Joomla article integration
- Consistency: Same content in both formats ensures documentation accuracy
- Accessibility: Different audiences prefer different formats
Format: Markdown with emoji section headers
# Changelog
All notable changes to [Extension Name] will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.2.0] - 2025-11-20
### π New Features
- **Feature Name**: Description of the feature
- Sub-feature detail
- Another detail
### π§ Improvements
- **Improvement**: Description
### π¦ Build & Infrastructure
- **Build change**: Description
### π Bug Fixes
- **Fixed issue**: Description
### π Documentation
- Documentation updates
## [1.1.0] - 2025-11-17
...- π New Features
- π§ Improvements
- π¦ Build & Infrastructure
- π Bug Fixes
- π Security
- π Documentation
- π¨ UI/UX
- π‘οΈ Breaking Changes
CRITICAL: HTML changelog MUST be article-ready (no <html>, <head>, or <body> tags, and no <style> tags)
The HTML changelog is designed to be copy-pasted directly into Joomla articles for release announcements and documentation pages. It uses semantic HTML that inherits styling from the Joomla template.
<div class="changelog-container">
<h1>π Extension Name - Changelog</h1>
<div class="intro">
<p><strong>All notable changes...</strong></p>
</div>
<h2>
<span class="version-badge">v1.2.0</span>
<span class="date">2025-11-20</span>
</h2>
<h3><span class="section-icon">π</span>New Features</h3>
<ul>
<li><strong>Feature</strong>: Description</li>
</ul>
<!-- More versions... -->
<div class="footer">
<p>© 2025 Your Name</p>
</div>
</div>β NEVER include:
<!DOCTYPE html>
<html>
<head>
<style>
<body>
</body>
</html>β ONLY include:
- Semantic HTML content only
<div>container with content- Standard HTML tags (h1, h2, h3, ul, li, p, strong, code, etc.)
Use proper semantic HTML that will inherit styling from the Joomla template:
<div class="changelog-container">
<h1>π Title</h1>
<h2>Version</h2>
<h3>Section</h3>
<ul>
<li><strong>Item</strong>: Description</li>
</ul>
</div>The Joomla template CSS will style these elements appropriately.
Add class names to elements that might need custom styling:
<div class="changelog-container"> <!-- Container class -->
<span class="version-badge"> <!-- Version badge -->
<span class="date"> <!-- Date -->
<span class="section-icon"> <!-- Emoji icon -->
<div class="intro"> <!-- Intro box -->
<div class="version-summary"> <!-- Summary box -->
<div class="footer"> <!-- Footer -->Users can add custom CSS to their Joomla template if they want to style these classes.
Use Unicode emojis directly (not HTML entities) for better display:
- β
Use:
π(direct emoji) - β Don't use:
🚀(HTML entity)
Modern Joomla supports UTF-8 emojis without issues.
- Write CHANGELOG.md first (easier to edit in markdown)
- Convert to CHANGELOG.html with proper styling
- Ensure content matches between both files
- Test HTML by pasting into a Joomla article
- Update version number in both files
- Add new version section at the top
- Categorize changes with emoji headers
- Keep formatting consistent
- Commit both files together
- Open CHANGELOG.html in text editor
- Copy entire contents (including
<style>tag) - In Joomla article editor, switch to Code view
- Paste the HTML
- Save article
- Preview to verify styling
CHANGELOG.md:
# Changelog
## [1.2.0] - 2025-11-20
### π New Features
- **Feature**: Description
### π§ Improvements
- **Improvement**: DescriptionCHANGELOG.html:
<div class="changelog-container">
<h1>π Extension - Changelog</h1>
<h2>
<span class="version-badge">v1.2.0</span>
<span class="date">2025-11-20</span>
</h2>
<h3><span class="section-icon">π</span>New Features</h3>
<ul>
<li><strong>Feature</strong>: Description</li>
</ul>
</div>Before committing changelogs, verify:
- Both CHANGELOG.md and CHANGELOG.html exist
- Content matches between both files
- CHANGELOG.md uses emoji section headers
- CHANGELOG.html has NO
<html>,<head>,<body>, or<style>tags - CHANGELOG.html uses only semantic HTML
- CHANGELOG.html has class names for optional styling
- Version numbers follow semantic versioning
- Dates are in YYYY-MM-DD format
- New version added at top of file
- Tested HTML by pasting into Joomla article
- Both files committed together
β DON'T:
<!DOCTYPE html>
<html>
<head>
<style>
.changelog { color: blue; }
</style>
</head>
<body>
<div class="changelog">...</div>
</body>
</html>β DO:
<div class="changelog-container">
<h1>π Extension - Changelog</h1>
<h2><span class="version-badge">v1.0.0</span></h2>
<h3><span class="section-icon">π</span>New Features</h3>
<ul>
<li><strong>Feature</strong>: Description</li>
</ul>
</div>Note: The Joomla template will provide all styling. If custom styling is needed, users can add CSS to their template that targets the class names provided.
From your Joomla project root directory, run:
git submodule add https://github.com/cybersalt/Joomla-Brain.git joomla-brain
git commit -m "Add Joomla-Brain submodule for development best practices"This creates a joomla-brain/ directory in your project with all the resources.
Reference Joomla-Brain in your build scripts. Example for batch scripts:
@echo off
REM Best Practices Reference: See joomla-brain/PACKAGE-BUILD-NOTES.md
REM Joomla 5 Checklist: See joomla-brain/JOOMLA5-CHECKLIST.md
REM ... your build commands here ...Create a .joomla-brain-config file in your project root to document your setup:
# Joomla-Brain Configuration
PROJECT_TYPE=module # or component, plugin, package
PROJECT_NAME=mod_yourmodule
JOOMLA_VERSION=5.0
MIN_PHP_VERSION=8.1.0
# Build Configuration
BUILD_SCRIPT=package-j5.bat
PACKAGE_NAME=mod_yourmodule_j5.zip
# Checklist References
CHECKLIST=joomla-brain/JOOMLA5-CHECKLIST.md
BUILD_NOTES=joomla-brain/PACKAGE-BUILD-NOTES.mdAdd a README.md to your project that references Joomla-Brain:
# Your Joomla Extension
## Development
This project follows best practices defined in the [Joomla-Brain](joomla-brain/) submodule.
### Key References
- **Joomla 5 Checklist**: [joomla-brain/JOOMLA5-CHECKLIST.md](joomla-brain/JOOMLA5-CHECKLIST.md)
- **Package Build Notes**: [joomla-brain/PACKAGE-BUILD-NOTES.md](joomla-brain/PACKAGE-BUILD-NOTES.md)
- **Best Practices**: [joomla-brain/README.md](joomla-brain/README.md)
### Building
See [joomla-brain/JOOMLA5-CHECKLIST.md](joomla-brain/JOOMLA5-CHECKLIST.md) before building releases.- Review the Checklist: Open
joomla-brain/JOOMLA5-CHECKLIST.md(orJOOMLA6-CHECKLIST.md) - Update Version Numbers: In all XML manifests
- Update Changelogs: Both
CHANGELOG.mdandCHANGELOG.htmlwith emoji headers - Build Package: Using your build script that follows Joomla-Brain standards
- Test Installation: On a clean Joomla site
- Reference Best Practices: Check
joomla-brain/README.mdfor coding standards - Troubleshooting Builds: See
joomla-brain/PACKAGE-BUILD-NOTES.mdfor common issues - File Encoding Issues: See
joomla-brain/FILE-CORRUPTION-FIX.md
You can either:
- Use the provided script: Copy
joomla-brain/build-package.batto your project root and customize - Reference in your script: Add comments pointing to Joomla-Brain documentation
To get the latest best practices and scripts:
git submodule update --remote joomla-brain
git add joomla-brain
git commit -m "Update Joomla-Brain to latest version"When team members clone your project:
git clone <your-repo-url>
cd <your-repo>
git submodule init
git submodule updateOr clone with submodules in one step:
git clone --recurse-submodules <your-repo-url>Team members can update to the latest Joomla-Brain:
git submodule update --remote joomla-brainSee the cs-category-grid-display repository for a complete example of Joomla-Brain integration.
Feel free to add more best practices and scripts to help Joomla developers!