Panel Screen Data Hooks
The nineteen hooks over what the customer, service, support and tool screens show, including client badges and the AI field helper.
Overview
The hooks that change what panel screens show live here: the customer detail and list, the service management cards, add-on buttons, critical notifications and the theme version lookup. On the ticket detail you can badge the related service.
Two differ from the rest: the profile tabs and the security accordion hand you a component object rather than an array, and you add through a method call.
Reference
Changing the customer extra details
Runs once the extra detail fields on a customer page are read.
Hook::add('filter:admin.client_detail.info', 10, function (&$client_info, $user_id) {
$client_info['acme_tier'] = Acme::tierOf($user_id);
});Adding a sub-tab to a customer profile
Runs while the vertical tabs of a customer profile are built. Add a tab of your own here.
Hook::add('filter:admin.client_detail.profile_subtabs', 10,
function (&$profileTab, $user, $user_id) {
// An object, not an array: call its method.
$profileTab->add('acme', 'Acme', Acme::renderPanel($user_id));
});Adding a section to the security area
Runs while the security settings accordion of a customer is built.
Hook::add('filter:admin.client_detail.security_accordion', 10,
function (&$secAccordion, $client_info, $user_id) {
$secAccordion->add('acme-sessions', 'Acme sessions', Acme::sessions($user_id));
});Changing the customer statistics
Runs once the counters on a customer page are worked out.
Hook::add('filter:admin.client_detail.statistics', 10, function (&$user_stats, $user_id) {
$user_stats['acme_open_cases'] = Acme::openCases($user_id);
});Changing the customer list filters
Runs once the filter criteria of the customer list are built.
Hook::add('filter:admin.client_list.filters', 10, function (&$dynamic_filter) {
// A new key must be handled in the listing query too.
$dynamic_filter['acme_tier'] = Acme::tierFilterValue();
});Changing the service management cards
Runs once the management cards on a service detail are prepared. Add a card of your own, hide one, or change the order.
cards holds the card content and layout says which card sits in which column and in what order. 🔴 Update both: a card missing from the layout never appears, and a layout key with no card is quietly skipped.Hook::add('filter:admin.service_detail.management_cards', 10, function (&$context) {
// Update BOTH: the card and the layout.
$context['cards']['acme'] = Acme::renderCard($context['service'] ?? []);
$context['layout']['right'][] = 'acme';
});Badging the related service of a ticket
Runs once when a ticket detail opens. Give a short badge for any service of the customer, such as the version a license reports. The badge of the selected service shows in the header of the Related Service card.
label (required, plain text) and optional title (tooltip), tone, icon (a bi-* class) and tab. With tab the badge opens that tab of the service page in a new window, for example management. The panel builds the address. A tab with anything other than letters, digits, - and _ leaves a plain badge.id, product_id, options and status. Read it; changing it does not change the service list.$badges. The label and the tooltip show as plain text, never as HTML.Hook::add('filter:admin.ticket_detail.service_badges', 20, function (&$badges, $services = []) {
foreach ((array) $services as $group)
foreach ((array) ($group['items'] ?? []) as $row) {
$plan = (string) ($row['options']['acme_plan'] ?? '');
if ($plan !== '') $badges[(int) $row['id']] = ['label' => $plan, 'icon' => 'bi-tag', 'tab' => 'management'];
}
});Changing the add-on settings buttons
Runs once the buttons on an add-on settings page are prepared.
Hook::add('filter:admin.addon_configure.buttons', 10, function (&$buttons) {
// Emptying it entirely leaves no buttons at all.
$buttons['acme-sync'] = [
'text' => 'Sync Acme',
'class' => 'btn btn-outline-primary',
'icon' => 'bi bi-arrow-repeat',
'onclick' => 'acmeSync()',
];
});Changing the address of an uploaded image
Runs after the address of an image uploaded into the editor is produced. This is how you move images onto a delivery network.
Hook::add('filter:admin.editor_upload_url', 10, function (&$url, $file_path) {
$url = Acme::pushToCdn($file_path) ?: $url;
});Following an image upload
Runs after an image is uploaded into the editor.
Hook::add('action:admin.editor_image_uploaded', 10, function ($file_path, $url) {
// The address is the one AFTER the filter ran.
Acme::noteAsset($file_path, $url);
});Reporting where a library image is used
Runs before the operator deletes an image from the Icon Library. Add your module's records: when the list is not empty the panel refuses the delete and shows their names. Runs from 5.0-beta.4 (with its latest fix package) onward; earlier versions do not run it.
['type', 'id', 'name']. It arrives with product, add-on, requirement and support access group records; you append your own.<date>/<file>), the value records keep in their icon field.#<id>, and the refusal shows the first ten names.Hook::add('filter:admin.icon_library.usage', 10, function (&$usage, $name) {
foreach (Acme::itemsUsingIcon($name) as $item)
$usage[] = ['type' => 'acme-item', 'id' => (int) $item['id'], 'name' => $item['title']];
});Adding to the critical notification list
Runs once the critical notification list in the panel header is prepared.
Hook::add('filter:admin.notifications.critical', 10, function (&$critical_transaction_notifications) {
// Fill every field the template expects.
foreach (Acme::criticalAlerts() as $a) $critical_transaction_notifications[] = $a;
});Following a panel notification
Runs when a system notification lands in the panel.
Hook::add('action:admin.notified', 10, function ($name, $data, $level, $event_id) {
// Zero means nothing was written.
if ($level === 'error' && $event_id) Acme::page($name, $data);
});Taking over the theme version lookup
Runs before a theme is asked about a new version. Return an answer and no remote call is made.
Hook::add('filter:admin.theme_version_check', 10, function ($key, $manifest) {
// A filled return means NO remote call is made.
if (!str_starts_with($key, 'Acme')) return null;
return Acme::versionJson($key);
});Adjusting the theme version response
Runs after the version response arrives and before it is decoded. Its sibling picks the source; this hook adjusts what came back.
Hook::add('filter:admin.theme_version_response', 10, function (&$key, &$manifest) {
// Both parameters are consumed: changing them has no effect.
Acme::noteVersionCheck($key);
});Adding a badge beside a client name
Lets a module mark a client account ("developer", "partner", "VIP") once. The core shows the mark wherever the panel names the client, in that screen's badge style: list cells (cell), the client info card (card) and the client detail header (detail). The core has no badge of its own; the slot is for modules.
label (required), icon (a bi-* class), link, title, key and tone (detail header only). An item without a label is dropped; every value is escaped by the core, so pass plain text.cell, card or detail.Hook::add('filter:admin.client_badges', 20, function (&$badges, $user_id, $context = '', $extra = []) {
if (!Acme::isPartner((int) $user_id)) return null;
$badges[] = ['key' => 'acme-partner', 'label' => 'Partner', 'icon' => 'bi-handshake'];
return null;
});Offering an engine to the AI field helper
The AI helper beside admin form fields has no provider of its own. A module declares itself here as an engine. It names its actions, its menu label and icon, and its models. With no engine declared, the trigger is not shown at all.
key (prose for text actions, translate for translation), actions, label, icon (without the bi- prefix), models and default_model. An item with no key or no actions is dropped.Hook::add('filter:admin.ai_assist.engines', 1, function (&$engines) {
if (!License::valid_addon('acme-ai')) return null;
$engines[] = ['key' => 'prose', 'actions' => ['improve', 'shorten', 'expand'], 'label' => 'Acme AI', 'icon' => 'robot'];
});Describing a field kind to the AI helper
A module that puts the AI trigger on its own screens adds its field kinds here. The description tells the model what the field is. It also shapes the suggestion: a name or a sentence, with or without a full stop. An unknown kind falls back to the core text profile.
kind => description map seeded with the core profiles. Write the description in English: it is an instruction to the model and does not follow the panel language.Hook::add('filter:admin.ai_assist.profiles', 1, function (&$profiles) {
$profiles['acme-plan-title'] = 'the title of a hosting plan shown on the price table. A short noun phrase, no closing period.';
});Answering an AI field request
Hands the prepared request to the engine that took the action. Your module calls its provider and writes the text into $result. The core never knows a provider name. If no engine took the action, this hook is not called at all.
text (required) and, if you like, model, engine, icon and plain. Put the provider's message in error and the operator sees it; an empty text becomes a "no suggestion" error.action, engine, kind, text, html, lang, target, model, system and user. Leave it alone when engine is not yours or the result is already filled.Hook::add('filter:admin.ai_assist.run', 1, function (&$result, $request = []) {
if ($result || (string) ($request['engine'] ?? '') !== 'prose') return null;
$answer = AcmeProvider::complete($request['system'], $request['user'], $request['model']);
$result = $answer === false ? ['error' => 'Provider is unreachable.'] : ['text' => $answer];
});Pitfalls
In the service management cards the content sits in one list and the layout in another. Add only the content and the card never appears; write only a name into the layout and that name is quietly skipped. Update both.
The profile sub-tabs and the security accordion hand you a component object. A listener treating it like an array fails; you add through the object’s own method.
Related Articles
Obrigado pela sua opinião!
A nossa equipa de apoio está disponível a qualquer hora para ajudar com o que não encontrar acima.