# Panel Product Screen Hooks

https://dev.wisecp.com/es/admin-product-screen-hooks

The twenty-two placement points of the product screens: the edit tabs, the list toolbars and the group, add-on and server forms.

## Overview

The placement points of the product screens live here: every tab of the product edit page, the list toolbars and the group, add-on, requirement and server forms.

On most form points the record you hold is **empty on a new record**. The list points hand you the product type: the same list serves every type from hosting to certificates.

## Reference

### Above the product edit form

uiadmin.product_edit.before_content

`admin/products/edit` above the form

Appears at the very top of the product edit page, above the tabs.

Parameters 1

$productarrayThe product being edited.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_edit.before_content', 10, function () {
    return '<div class="alert alert-info">Acme</div>';
});
```

### A new tab on the product edit page

uiadmin.product_edit.tabs

`admin/products/edit` an object, not an array

Runs once the product tabs are built. You do not return HTML: you add the tab through the object’s method.

Parameters 2

$tabobjectThe tab object.

$productarrayThe product being edited.

Return 1

voidThe return is **not used**. This point shows no HTML.

Listener PHP

```php
Hook::add('ui:admin.product_edit.tabs', 10, function ($tab, $product) {
    $tab->add('acme', ['title' => 'Acme', 'content' => Acme::renderTab($product)]);
});
```

### The top of the details tab

uiadmin.product_edit.details.top

`admin/products/edit` top of the tab

Appears above the basic detail fields of the product.

Parameters 1

$detailarrayThe active product record: type, options, language data and module data.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_edit.details.top', 10, function () {
    return '<div class="alert alert-info">Acme</div>';
});
```

### The bottom of the details tab

uiadmin.product_edit.details.bottom

`admin/products/edit` bottom of the tab

Appears below the basic detail fields. The place to put a product setting of your own inside the form.

Parameters 1

$detailarrayThe active product record: type, options, language data and module data.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_edit.details.bottom', 10, function ($detail) {
    // Alan formun icinde: kaydetmeyi filter:product.save_data ile yakalayin.
    $v = htmlspecialchars((string) ($detail['options']['acme_sku'] ?? ''));

    return '<div class="mb-3"><label>Acme SKU</label>'
         . '<input class="form-control" name="options[acme_sku]" value="' . $v . '"></div>';
});
```

### The bottom of the pricing tab

uiadmin.product_edit.pricing.bottom

`admin/products/edit` the pricing tab

Appears below the price table. The place for a pricing rule or note of your own.

Parameters 1

$detailarrayThe active product record: type, options, language data and module data.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_edit.pricing.bottom', 10, function () {
    return '<div class="alert alert-info">Acme</div>';
});
```

### The bottom of the add-ons tab

uiadmin.product_edit.addons.bottom

`admin/products/edit` the add-on list is text

Appears below the add-on selection of the product.

Parameters 1

$detailarrayThe active product record. The linked add-ons field is **comma-separated text**, not an array: split it to work with it.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_edit.addons.bottom', 10, function () {
    return '<div class="alert alert-info">Acme</div>';
});
```

### The bottom of the automation tab

uiadmin.product_edit.automation.bottom

`admin/products/edit` module settings

Appears below the server and module settings.

Parameters 1

$detailarrayThe active product record: type, options, language data and module data.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_edit.automation.bottom', 10, function () {
    return '<div class="alert alert-info">Acme</div>';
});
```

### The bottom of the other tab

uiadmin.product_edit.other.bottom

`admin/products/edit` the other tab

Appears below the remaining product settings.

Parameters 1

$detailarrayThe active product record: type, options, language data and module data.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_edit.other.bottom', 10, function () {
    return '<div class="alert alert-info">Acme</div>';
});
```

### The very bottom of the product edit page

uiadmin.product_edit.bottom

`admin/products/edit` end of the page

Appears at the very end of the page, outside the tabs.

Parameters 1

$productarrayThe product being edited.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_edit.bottom', 10, function () {
    return '<div class="alert alert-info">Acme</div>';
});
```

### The product list toolbar

uiadmin.product_list.toolbar

`admin/products` the type arrives

Appears in the button group above the product list.

Parameters 1

$typestringThe product type being listed: hosting, server, software, text message, certificate or special. The same list serves every type: check this if what you add should not appear on all of them.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_list.toolbar', 10, function ($type) {
    // Ayni liste her tip icin kullanilir.
    if ($type !== 'hosting') return null;

    return '<a href="/acme/bulk" class="btn btn-outline-secondary">Acme</a>';
});
```

### Above the table on the product list

uiadmin.product_list.before_table

`admin/products` the type arrives

Appears immediately above the product table.

Parameters 1

$typestringThe product type being listed: hosting, server, software, text message, certificate or special. The same list serves every type: check this if what you add should not appear on all of them.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_list.before_table', 10, function ($type) {
    // Ayni liste her tip icin kullanilir.
    if ($type !== 'hosting') return null;

    return '<a href="/acme/bulk" class="btn btn-outline-secondary">Acme</a>';
});
```

### Above the product creation form

uiadmin.product_add.before_content

`admin/products` no parameters

Appears above the new product form.

Parameters 0

—It takes no parameters.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_add.before_content', 10, function () {
    return '<div class="alert alert-info">Acme</div>';
});
```

### Below the product creation form

uiadmin.product_add.bottom

`admin/products` no parameters

Appears below the new product form.

Parameters 0

—It takes no parameters.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_add.bottom', 10, function () {
    return '<div class="alert alert-info">Acme</div>';
});
```

### The add-on list toolbar

uiadmin.product_addon_list.toolbar

`admin/products` no parameters

Appears in the toolbar of the product add-on list.

Parameters 0

—It takes no parameters.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_addon_list.toolbar', 10, function () {
    return '<div class="alert alert-info">Acme</div>';
});
```

### The group list toolbar

uiadmin.product_group_list.toolbar

`admin/products` no parameters

Appears in the toolbar of the product group list.

Parameters 0

—It takes no parameters.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_group_list.toolbar', 10, function () {
    return '<div class="alert alert-info">Acme</div>';
});
```

### The server list toolbar

uiadmin.product_server_list.toolbar

`admin/products/servers` no parameters

Appears in the toolbar of the server list.

Parameters 0

—It takes no parameters.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_server_list.toolbar', 10, function () {
    return '<div class="alert alert-info">Acme</div>';
});
```

### The bottom of the add-on form

uiadmin.product_addon_edit.bottom

`admin/products` empty when creating

Appears below the product add-on add and edit form.

Parameters 1

$detailarrayThe add-on being edited. It is **empty on a new record**: test before reaching for an id.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_addon_edit.bottom', 10, function () {
    return '<div class="alert alert-info">Acme</div>';
});
```

### The bottom of the requirement form

uiadmin.product_requirement_edit.bottom

`admin/products` empty when creating

Appears below the product requirement form.

Parameters 1

$detailarrayThe requirement being edited. It is **empty on a new record**: test before reaching for an id.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_requirement_edit.bottom', 10, function () {
    return '<div class="alert alert-info">Acme</div>';
});
```

### The bottom of the server form

uiadmin.product_server_edit.bottom

`admin/products/servers` empty when creating

Appears below the server add and edit form.

Parameters 1

$detailarrayThe server being edited. It is **empty on a new record**: test before reaching for an id.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_server_edit.bottom', 10, function () {
    return '<div class="alert alert-info">Acme</div>';
});
```

### The bottom of the server group form

uiadmin.product_server_group_edit.bottom

`admin/products/servers` empty when creating

Appears below the server group form.

Parameters 1

$detailarrayThe server group being edited. It is **empty on a new record**: test before reaching for an id.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_server_group_edit.bottom', 10, function () {
    return '<div class="alert alert-info">Acme</div>';
});
```

### Above the group form

uiadmin.product_group_edit.before_content

`admin/products` group or category

Appears above the product group and category form.

Parameters 2

$detailarrayThe group or category being edited. It is **empty on a new record**: test before reaching for an id.

$categorystringThe category context. **Empty means a top group**, filled means a category kind: the same form does two jobs.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_group_edit.before_content', 10, function () {
    return '<div class="alert alert-info">Acme</div>';
});
```

### Below the group form

uiadmin.product_group_edit.bottom

`admin/products` group or category

Appears below the same form.

Parameters 2

$detailarrayThe group or category being edited. It is **empty on a new record**: test before reaching for an id.

$categorystringThe category context. **Empty means a top group**, filled means a category kind: the same form does two jobs.

Return 1

string|nullThe **HTML you return appears**. With several listeners the outputs are appended one after another in priority order. Empty text, `null` and `false` are skipped, so return `null` when you have nothing to add. The output enters the page unescaped — clean anything coming from outside yourself.

Listener PHP

```php
Hook::add('ui:admin.product_group_edit.bottom', 10, function () {
    return '<div class="alert alert-info">Acme</div>';
});
```

## Pitfalls

> **Adding a form field does not save it**
> 
> A field you put at these points is **only visible**. For the value to be kept you must catch it in the save filter; otherwise an administrator fills it in, saves, and the value quietly disappears.

> **The group form does two jobs**
> 
> On the group edit points an **empty category context means a top group** while a filled one means a category kind is being edited. A listener that does not tell them apart shows a category-only field on the group form.

## Related Articles

- [Product Data and Catalogue Hooks](https://dev.wisecp.com/en/product-data-hooks)
- [Server Record Hooks](https://dev.wisecp.com/en/server-record-hooks)
- [Management Panel Hooks](https://dev.wisecp.com/en/hooks-in-the-management-panel)
