Plugin Toolbar Buttons & Internal Pages
J2Commerce plugins use two separate toolbar contexts:
-
Plugin config page (
com_plugins&task=plugin.edit) — toolbar buttons are injected via a customFormFieldin the XML manifest. Only action buttons (Manage, Import, Add) belong here; Joomla already provides Save, Close, and Help. -
Internal pages (
com_j2commerce&view=apps&app={plugin}&layout={page}) — toolbar buttons are built in the plugin'ssetupToolbar()method called fromonAfterDispatch(). The Settings and Help buttons belong here, since the user needs a way to navigate back to plugin configuration.
Plugin Config Page (com_plugins) Internal Page (com_j2commerce&view=apps)
┌──────────────────────────────────┐ ┌──────────────────────────────────────────┐
│ [Manage] [Import] [Add] [Save] │ │ [Add] [Actions▼] ··· [Settings] [Help] │
│ ↑ FormField │ │ ← left group → ← right group → │
│ Joomla provides Save/Close/Help │ │ inlinehelp() spacer creates the gap │
└──────────────────────────────────┘ └──────────────────────────────────────────┘
How Right-Alignment Works
Joomla's media/legacy/js/toolbar.js adds the Bootstrap ms-auto class to specific toolbar button wrappers at DOM ready. The priority logic:
- If
#toolbar-inlinehelpexists → it getsms-auto(early return) - Else if
#toolbar-helpexists and no#toolbar-options→ help getsms-auto - Else if
#toolbar-optionsexists → options getsms-auto
Key insight: ms-auto on a flex child pushes it and all subsequent siblings to the right. To get both Settings and Help on the right side, you must call $toolbar->inlinehelp() before them. The inlinehelp() button is automatically hidden (d-none) when no inline help descriptions exist on the page, but it still carries the ms-auto class that creates the visual gap.
Plugin Config Page: FormField Toolbar
How It Works
When Joomla renders a plugin's configuration page, it processes the plugin's XML form fields. A custom FormField subclass overrides getInput() to inject navigation buttons into the toolbar as a side effect, returning an empty string so no visible form element appears.
Do not add Settings or Help buttons here — Joomla's plugin editor already provides Save/Apply/Close and its own Help button. Only add action buttons that navigate to internal pages.
Implementation
<?php
declare(strict_types=1);
namespace J2Commerce\Plugin\J2Commerce\{PluginName}\Field;
\defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormField;
use Joomla\CMS\Toolbar\Button\LinkButton;
use Joomla\CMS\Toolbar\Toolbar;
class {Name}ToolbarField extends FormField
{
protected $type = '{Name}Toolbar';
protected function getInput(): string
{
$app = Factory::getApplication();
if (!$app->isClient('administrator')) {
return '';
}
$app->getLanguage()->load(
'plg_j2commerce_{plugin_element}',
JPATH_ADMINISTRATOR
);
$toolbar = Toolbar::getInstance('toolbar');
// Prepend in REVERSE order — last prepended appears leftmost
$manageBtn = new LinkButton('{plugin}-manage');
$manageBtn->text('PLG_J2COMMERCE_{PLUGIN}_MANAGE')
->url('index.php?option=com_j2commerce&view=apps&app={plugin_element}&layout=list')
->icon('fa-solid fa-list');
$toolbar->prependButton($manageBtn);
return '';
}
protected function getLabel(): string
{
return '';
}
}
XML Manifest Registration
Add the toolbar field as the first field in the basic fieldset:
<config>
<fields name="params">
<fieldset name="basic" label="J2COMMERCE_BASIC_SETTINGS">
<field
name="{plugin}_toolbar"
type="{Name}Toolbar"
addfieldprefix="J2Commerce\Plugin\J2Commerce\{PluginName}\Field"
/>
<!-- Other configuration fields follow -->
</fieldset>
</fields>
</config>
Internal Pages: setupToolbar()
Internal pages are rendered by the plugin's onAfterDispatch() method. Since these pages replace the component output buffer, the plugin is responsible for building the entire toolbar — including Settings and Help buttons.
Implementation
In the plugin's main Extension class:
private function setupToolbar(string $layout): void
{
$toolbar = Toolbar::getInstance('toolbar');
$toolbar->setItems([]);
switch ($layout) {
case 'list':
ToolbarHelper::title(
Text::_('PLG_J2COMMERCE_{PLUGIN}_MANAGE'),
'fa-solid fa-list'
);
$toolbar->linkButton('{plugin}-add', 'PLG_J2COMMERCE_{PLUGIN}_ADD')
->url('index.php?option=com_j2commerce&view=apps&app={element}&layout=add')
->icon('fa-solid fa-plus');
break;
case 'edit':
ToolbarHelper::title(Text::_('PLG_J2COMMERCE_{PLUGIN}_EDIT'), 'fa-solid fa-pen');
ToolbarHelper::apply('');
ToolbarHelper::save('');
ToolbarHelper::cancel('cancel');
break;
}
// Right-side buttons — shared across all layouts
// inlinehelp() gets ms-auto from toolbar.js, pushing everything after it right
$toolbar->inlinehelp();
// Settings button — links back to plugin configuration
$pluginEditUrl = $this->getPluginEditUrl();
if ($pluginEditUrl !== '') {
$toolbar->linkButton('{plugin}-settings', 'PLG_J2COMMERCE_{PLUGIN}_SETTINGS')
->url($pluginEditUrl)
->icon('fa-solid fa-gear');
}
// Help button — always rightmost
$toolbar->help('', false, 'https://docs.j2commerce.com/v6/apps/{element}');
}
getPluginEditUrl() Helper
private function getPluginEditUrl(): string
{
$db = $this->getDatabase();
$type = 'plugin';
$element = '{plugin_element}';
$folder = 'j2commerce';
$query = $db->getQuery(true)
->select($db->quoteName('extension_id'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('type') . ' = :type')
->where($db->quoteName('element') . ' = :element')
->where($db->quoteName('folder') . ' = :folder')
->bind(':type', $type)
->bind(':element', $element)
->bind(':folder', $folder);
$extensionId = (int) ($db->setQuery($query, 0, 1)->loadResult() ?: 0);
if ($extensionId <= 0) {
return '';
}
return 'index.php?option=com_plugins&task=plugin.edit&extension_id=' . $extensionId;
}
Use a direct DB query on #__extensions instead of PluginHelper::getPlugin(). The DB query works regardless of whether the plugin is currently enabled, and avoids the false return value that PluginHelper::getPlugin() returns for disabled plugins (which causes TypeError with strict_types=1).
Settings Button
The Settings button navigates the admin user from an internal plugin page back to the plugin's Joomla configuration form.
Conventions
| Property | Value |
|---|---|
| Icon | fa-solid fa-gear |
| Language key | PLG_J2COMMERCE_{PLUGIN}_SETTINGS (custom per-plugin string) |
| Placement | Right side — after inlinehelp(), before Help |
| URL format | index.php?option=com_plugins&task=plugin.edit&extension_id={id} |
Where NOT to Put It
Do not add the Settings button on the plugin config page (com_plugins&task=plugin.edit). That page already IS the settings page — linking to yourself is circular.
Help Button
The Help button opens the plugin's documentation page on docs.j2commerce.com.
Implementation
$toolbar->help('', false, 'https://docs.j2commerce.com/v6/apps/{element}');
This renders a standard Joomla help button with the question-circle icon. Always the rightmost button.
URL Patterns by Plugin Type
| Plugin type | URL pattern | Example |
|---|---|---|
| App plugin | /v6/apps-and-extensions/apps/{element} | /v6/apps-and-extensions/apps/app-reviews |
| Payment plugin | /v6/payment/{element} | /v6/payment/stripe |
| Shipping plugin | /v6/shipping/{element} | /v6/shipping/standard |
| Report plugin | /v6/reports/{element} | /v6/reports/itemised |
Button Ordering on Internal Pages
[Action Buttons...] [inlinehelp*] [Settings] [Help]
←── left group ──→ ← spacer → ←── right group ──→
* inlinehelp is hidden (d-none) but carries ms-auto
- Left side: Plugin-specific action buttons — Add, Actions dropdown, Import, Back, etc.
- Spacer:
$toolbar->inlinehelp()— hidden but providesms-autovia toolbar.js - Right side: Settings, then Help (always rightmost)
Creating Internal Plugin Pages
Internal pages are rendered by the plugin's onAfterDispatch() event handler. The plugin checks the URL parameters, builds the toolbar, renders the view, and injects the HTML into the component output buffer.
URL Structure
index.php?option=com_j2commerce&view=apps&app={plugin_element}&layout={layout_name}
Template Files
Place layout templates in the plugin's tmpl/ directory:
plugins/j2commerce/{plugin_element}/
└── tmpl/
├── default.php ← Main plugin dashboard / overview
├── list.php ← Internal page: list view
├── edit.php ← Internal page: single item edit
├── import.php ← Internal page: import tool
└── add.php ← Internal page: add new item
Each template receives $displayData as an array (via FileLayout / PluginLayoutTrait), not as individual variables.
onAfterDispatch Pattern
public function onAfterDispatch(): void
{
$app = $this->getApplication();
$input = $app->getInput();
if ($input->getCmd('option') !== 'com_j2commerce') {
return;
}
if (!$app->isClient('administrator') || $input->getCmd('app') !== '{plugin_element}') {
return;
}
$layout = $input->getCmd('layout', 'default');
// Handle POST actions first
if ($input->getMethod() === 'POST') {
$this->handlePostAction($input);
return;
}
// Set up toolbar for the current layout
$this->setupToolbar($layout);
// Render the view and inject into component buffer
$html = $this->renderView($layout);
if ($html !== '') {
$app->getDocument()->setBuffer($html, 'component');
}
}
Checklist for New Plugins
When creating a new J2Commerce plugin with internal pages:
- Create
src/Field/{Name}ToolbarField.php— action buttons only (Manage, Add, etc.) - Add the field to the XML manifest's
basicfieldset as the first field - In the Extension class, create
setupToolbar(string $layout)method - Add action buttons inside the switch/case for each layout
- After the switch, add shared right-side buttons in this order:
$toolbar->inlinehelp()— spacer withms-auto- Settings button (
fa-solid fa-gear) $toolbar->help(...)— always last
- Create
getPluginEditUrl()private method using DB query on#__extensions - Add
PLG_J2COMMERCE_{PLUGIN}_SETTINGS="Settings"to the.inilanguage file - All icons must use
fa-solid fa-*format (not legacyicon-*) - Sync language strings to both plugin dir and
administrator/language/en-GB/